-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
spanner: benchmark general array decoders
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
Showing
2 changed files
with
210 additions
and
107 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters