Skip to content

Commit

Permalink
spanner: benchmark general array decoders
Browse files Browse the repository at this point in the history
In an attempt to reduce code duplication, I experimented with
two general-purpose array decoders, one using a passed-in function
and one using reflection.

The function-based decoder is about 15% slower. The reflection-based
decoded is around 3x slower.

Using reflection is obviously unacceptable. The function-based decoder
reduces duplication slightly. I decided not to use it, partly since
the code around array decoding may change if #749 is adopted.

I also moved all the benchmarks to the same file, and used sub-benchmarks.

Change-Id: If14cff25bdcd3541447000af84eb998bedc008d6
Reviewed-on: https://code-review.googlesource.com/16350
Reviewed-by: kokoro <[email protected]>
Reviewed-by: Vikas Kedia <[email protected]>
  • Loading branch information
jba committed Sep 5, 2017
1 parent 1bde8d1 commit 6e42463
Show file tree
Hide file tree
Showing 2 changed files with 210 additions and 107 deletions.
210 changes: 210 additions & 0 deletions spanner/value_benchmarks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
/*
Copyright 2017 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// +build go1.7
// for sub-benchmarks

package spanner

import (
"reflect"
"strconv"
"testing"

"cloud.google.com/go/civil"
proto3 "github.com/golang/protobuf/ptypes/struct"
sppb "google.golang.org/genproto/googleapis/spanner/v1"
)

func BenchmarkEncodeIntArray(b *testing.B) {
for _, s := range []struct {
name string
f func(a []int) (*proto3.Value, *sppb.Type, error)
}{
{"Orig", encodeIntArrayOrig},
{"Func", encodeIntArrayFunc},
{"Reflect", encodeIntArrayReflect},
} {
b.Run(s.name, func(b *testing.B) {
for _, size := range []int{1, 10, 100, 1000} {
a := make([]int, size)
b.Run(strconv.Itoa(size), func(b *testing.B) {
for i := 0; i < b.N; i++ {
s.f(a)
}
})
}
})
}
}

func encodeIntArrayOrig(a []int) (*proto3.Value, *sppb.Type, error) {
vs := make([]*proto3.Value, len(a))
var err error
for i := range a {
vs[i], _, err = encodeValue(a[i])
if err != nil {
return nil, nil, err
}
}
return listProto(vs...), listType(intType()), nil
}

func encodeIntArrayFunc(a []int) (*proto3.Value, *sppb.Type, error) {
v, err := encodeArray(len(a), func(i int) interface{} { return a[i] })
if err != nil {
return nil, nil, err
}
return v, listType(intType()), nil
}

func encodeIntArrayReflect(a []int) (*proto3.Value, *sppb.Type, error) {
v, err := encodeArrayReflect(a)
if err != nil {
return nil, nil, err
}
return v, listType(intType()), nil
}

func encodeArrayReflect(a interface{}) (*proto3.Value, error) {
va := reflect.ValueOf(a)
len := va.Len()
vs := make([]*proto3.Value, len)
var err error
for i := 0; i < len; i++ {
vs[i], _, err = encodeValue(va.Index(i).Interface())
if err != nil {
return nil, err
}
}
return listProto(vs...), nil
}

func BenchmarkDecodeGeneric(b *testing.B) {
v := stringProto("test")
t := stringType()
var g GenericColumnValue
b.ResetTimer()
for i := 0; i < b.N; i++ {
decodeValue(v, t, &g)
}
}

func BenchmarkDecodeArray(b *testing.B) {
for _, size := range []int{1, 10, 100, 1000} {
vals := make([]*proto3.Value, size)
for i := 0; i < size; i++ {
vals[i] = dateProto(d1)
}
lv := &proto3.ListValue{Values: vals}
b.Run(strconv.Itoa(size), func(b *testing.B) {
for _, s := range []struct {
name string
decode func(*proto3.ListValue)
}{
{"DateDirect", decodeArray_Date_direct},
{"DateFunc", decodeArray_Date_func},
{"DateReflect", decodeArray_Date_reflect},
{"StringDirect", decodeArray_String_direct},
{"StringFunc", decodeArray_String_func},
{"StringReflect", decodeArray_String_reflect},
} {
b.Run(s.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
s.decode(lv)
}
})
}
})

}
}

func decodeArray_Date_direct(pb *proto3.ListValue) {
a := make([]civil.Date, len(pb.Values))
t := dateType()
for i, v := range pb.Values {
if err := decodeValue(v, t, &a[i]); err != nil {
panic(err)
}
}
}

func decodeArray_Date_func(pb *proto3.ListValue) {
a := make([]civil.Date, len(pb.Values))
if err := decodeArray_func(pb, "DATE", dateType(), func(i int) interface{} { return &a[i] }); err != nil {
panic(err)
}
}

func decodeArray_Date_reflect(pb *proto3.ListValue) {
var a []civil.Date
if err := decodeArray_reflect(pb, "DATE", dateType(), &a); err != nil {
panic(err)
}
}

func decodeArray_String_direct(pb *proto3.ListValue) {
a := make([]string, len(pb.Values))
t := stringType()
for i, v := range pb.Values {
if err := decodeValue(v, t, &a[i]); err != nil {
panic(err)
}
}
}

func decodeArray_String_func(pb *proto3.ListValue) {

a := make([]string, len(pb.Values))
if err := decodeArray_func(pb, "STRING", stringType(), func(i int) interface{} { return &a[i] }); err != nil {
panic(err)
}
}

func decodeArray_String_reflect(pb *proto3.ListValue) {
var a []string
if err := decodeArray_reflect(pb, "STRING", stringType(), &a); err != nil {
panic(err)
}
}

func decodeArray_func(pb *proto3.ListValue, name string, typ *sppb.Type, elptr func(int) interface{}) error {
if pb == nil {
return errNilListValue(name)
}
for i, v := range pb.Values {
if err := decodeValue(v, typ, elptr(i)); err != nil {
return errDecodeArrayElement(i, v, name, err)
}
}
return nil
}

func decodeArray_reflect(pb *proto3.ListValue, name string, typ *sppb.Type, aptr interface{}) error {
if pb == nil {
return errNilListValue(name)
}
av := reflect.ValueOf(aptr).Elem()
av.Set(reflect.MakeSlice(av.Type(), len(pb.Values), len(pb.Values)))
for i, v := range pb.Values {
if err := decodeValue(v, typ, av.Index(i).Addr().Interface()); err != nil {
av.Set(reflect.Zero(av.Type())) // reset slice to nil
return errDecodeArrayElement(i, v, name, err)
}
}
return nil
}
107 changes: 0 additions & 107 deletions spanner/value_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -518,110 +518,3 @@ func TestGenericColumnValue(t *testing.T) {
}
}
}

func runBench(b *testing.B, size int, f func(a []int) (*proto3.Value, *sppb.Type, error)) {
a := make([]int, size)
for i := 0; i < b.N; i++ {
f(a)
}
}

func BenchmarkEncodeIntArrayOrig1(b *testing.B) {
runBench(b, 1, encodeIntArrayOrig)
}

func BenchmarkEncodeIntArrayOrig10(b *testing.B) {
runBench(b, 10, encodeIntArrayOrig)
}

func BenchmarkEncodeIntArrayOrig100(b *testing.B) {
runBench(b, 100, encodeIntArrayOrig)
}

func BenchmarkEncodeIntArrayOrig1000(b *testing.B) {
runBench(b, 1000, encodeIntArrayOrig)
}

func BenchmarkEncodeIntArrayFunc1(b *testing.B) {
runBench(b, 1, encodeIntArrayFunc)
}

func BenchmarkEncodeIntArrayFunc10(b *testing.B) {
runBench(b, 10, encodeIntArrayFunc)
}

func BenchmarkEncodeIntArrayFunc100(b *testing.B) {
runBench(b, 100, encodeIntArrayFunc)
}

func BenchmarkEncodeIntArrayFunc1000(b *testing.B) {
runBench(b, 1000, encodeIntArrayFunc)
}

func BenchmarkEncodeIntArrayReflect1(b *testing.B) {
runBench(b, 1, encodeIntArrayReflect)
}

func BenchmarkEncodeIntArrayReflect10(b *testing.B) {
runBench(b, 10, encodeIntArrayReflect)
}

func BenchmarkEncodeIntArrayReflect100(b *testing.B) {
runBench(b, 100, encodeIntArrayReflect)
}

func BenchmarkEncodeIntArrayReflect1000(b *testing.B) {
runBench(b, 1000, encodeIntArrayReflect)
}

func encodeIntArrayOrig(a []int) (*proto3.Value, *sppb.Type, error) {
vs := make([]*proto3.Value, len(a))
var err error
for i := range a {
vs[i], _, err = encodeValue(a[i])
if err != nil {
return nil, nil, err
}
}
return listProto(vs...), listType(intType()), nil
}

func encodeIntArrayFunc(a []int) (*proto3.Value, *sppb.Type, error) {
v, err := encodeArray(len(a), func(i int) interface{} { return a[i] })
if err != nil {
return nil, nil, err
}
return v, listType(intType()), nil
}

func encodeIntArrayReflect(a []int) (*proto3.Value, *sppb.Type, error) {
v, err := encodeArrayReflect(a)
if err != nil {
return nil, nil, err
}
return v, listType(intType()), nil
}

func encodeArrayReflect(a interface{}) (*proto3.Value, error) {
va := reflect.ValueOf(a)
len := va.Len()
vs := make([]*proto3.Value, len)
var err error
for i := 0; i < len; i++ {
vs[i], _, err = encodeValue(va.Index(i).Interface())
if err != nil {
return nil, err
}
}
return listProto(vs...), nil
}

func BenchmarkDecodeGeneric(b *testing.B) {
v := stringProto("test")
t := stringType()
var g GenericColumnValue
b.ResetTimer()
for i := 0; i < b.N; i++ {
decodeValue(v, t, &g)
}
}

0 comments on commit 6e42463

Please sign in to comment.