blob: d415fa4a7d6c22965fbdff591714d1085284d6c2 [file] [log] [blame]
// Copyright 2017 Google LLC
//
// 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.
package spanner
import (
"fmt"
"math"
"reflect"
"strconv"
"testing"
"cloud.google.com/go/civil"
sppb "cloud.google.com/go/spanner/apiv1/spannerpb"
"google.golang.org/api/iterator"
proto3 "google.golang.org/protobuf/types/known/structpb"
)
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 BenchmarkDecodeString(b *testing.B) {
v := stringProto("test")
t := stringType()
var s string
b.ResetTimer()
for i := 0; i < b.N; i++ {
decodeValue(v, t, &s)
}
}
func BenchmarkDecodeCustomString(b *testing.B) {
v := stringProto("test")
t := stringType()
type CustomString string
var s CustomString
b.ResetTimer()
for i := 0; i < b.N; i++ {
decodeValue(v, t, &s)
}
}
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", decodeArrayDateDirect},
{"DateFunc", decodeArrayDateFunc},
{"DateReflect", decodeArrayDateReflect},
{"StringDecodeStringArray", decodeStringArrayWrap},
{"StringDirect", decodeArrayStringDirect},
{"StringFunc", decodeArrayStringFunc},
{"StringReflect", decodeArrayStringReflect},
} {
b.Run(s.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
s.decode(lv)
}
})
}
})
}
}
func decodeArrayDateDirect(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 decodeArrayDateFunc(pb *proto3.ListValue) {
a := make([]civil.Date, len(pb.Values))
if err := decodeArrayFunc(pb, "DATE", dateType(), func(i int) interface{} { return &a[i] }); err != nil {
panic(err)
}
}
func decodeArrayDateReflect(pb *proto3.ListValue) {
var a []civil.Date
if err := decodeArrayReflect(pb, "DATE", dateType(), &a); err != nil {
panic(err)
}
}
func decodeStringArrayWrap(pb *proto3.ListValue) {
if _, err := decodeStringArray(pb); err != nil {
panic(err)
}
}
func decodeArrayStringDirect(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 decodeArrayStringFunc(pb *proto3.ListValue) {
a := make([]string, len(pb.Values))
if err := decodeArrayFunc(pb, "STRING", stringType(), func(i int) interface{} { return &a[i] }); err != nil {
panic(err)
}
}
func decodeArrayStringReflect(pb *proto3.ListValue) {
var a []string
if err := decodeArrayReflect(pb, "STRING", stringType(), &a); err != nil {
panic(err)
}
}
func decodeArrayFunc(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 decodeArrayReflect(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
}
func BenchmarkScan(b *testing.B) {
scanMethods := []string{"row.Column()", "row.ToStruct()", "row.SelectAll()"}
for _, method := range scanMethods {
for k := 0.; k <= 20; k++ {
n := int(math.Pow(2, k))
b.Run(fmt.Sprintf("%s/%d", method, n), func(b *testing.B) {
b.StopTimer()
var rows []struct {
ID int64
Name string
Active bool
City string
State string
}
for i := 0; i < n; i++ {
rows = append(rows, struct {
ID int64
Name string
Active bool
City string
State string
}{int64(i), fmt.Sprintf("name-%d", i), true, "city", "state"})
}
src := mockBenchmarkIterator(b, rows)
for i := 0; i < b.N; i++ {
it := *src
var res []struct {
ID int64
Name string
Active bool
City string
State string
}
b.StartTimer()
switch method {
case "row.SelectAll()":
if err := SelectAll(&it, &res); err != nil {
b.Fatal(err)
}
_ = res
break
default:
for {
row, err := it.Next()
if err == iterator.Done {
break
} else if err != nil {
b.Fatal(err)
}
var r struct {
ID int64
Name string
Active bool
City string
State string
}
if method == "row.Column()" {
err = row.Columns(&r.ID, &r.Name, &r.Active, &r.City, &r.State)
if err != nil {
b.Fatal(err)
}
} else {
err = row.ToStruct(&r)
if err != nil {
b.Fatal(err)
}
}
res = append(res, r)
}
it.Stop()
_ = res
}
}
})
}
}
}
func BenchmarkScan100RowsUsingSelectAll(b *testing.B) {
var rows []struct {
ID int64
Name string
}
for i := 0; i < 100; i++ {
rows = append(rows, struct {
ID int64
Name string
}{int64(i), fmt.Sprintf("name-%d", i)})
}
src := mockBenchmarkIterator(b, rows)
b.ResetTimer()
for n := 0; n < b.N; n++ {
it := *src
var res []struct {
ID int64
Name string
}
if err := SelectAll(&it, &res); err != nil {
b.Fatal(err)
}
_ = res
}
}
func BenchmarkScan100RowsUsingToStruct(b *testing.B) {
var rows []struct {
ID int64
Name string
}
for i := 0; i < 100; i++ {
rows = append(rows, struct {
ID int64
Name string
}{int64(i), fmt.Sprintf("name-%d", i)})
}
src := mockBenchmarkIterator(b, rows)
b.ResetTimer()
for n := 0; n < b.N; n++ {
it := *src
var res []struct {
ID int64
Name string
}
for {
row, err := it.Next()
if err == iterator.Done {
break
} else if err != nil {
b.Fatal(err)
}
var r struct {
ID int64
Name string
}
err = row.ToStruct(&r)
if err != nil {
b.Fatal(err)
}
res = append(res, r)
}
it.Stop()
_ = res
}
}
func BenchmarkScan100RowsUsingColumns(b *testing.B) {
var rows []struct {
ID int64
Name string
}
for i := 0; i < 100; i++ {
rows = append(rows, struct {
ID int64
Name string
}{int64(i), fmt.Sprintf("name-%d", i)})
}
src := mockBenchmarkIterator(b, rows)
b.ResetTimer()
for n := 0; n < b.N; n++ {
it := *src
var res []struct {
ID int64
Name string
}
for {
row, err := it.Next()
if err == iterator.Done {
break
} else if err != nil {
b.Fatal(err)
}
var r struct {
ID int64
Name string
}
err = row.Columns(&r.ID, &r.Name)
if err != nil {
b.Fatal(err)
}
res = append(res, r)
}
it.Stop()
_ = res
}
}
func mockBenchmarkIterator[T any](t testing.TB, rows []T) *mockIteratorImpl {
var v T
var colNames []string
numCols := reflect.TypeOf(v).NumField()
for i := 0; i < numCols; i++ {
f := reflect.TypeOf(v).Field(i)
colNames = append(colNames, f.Name)
}
var srows []*Row
for _, e := range rows {
var vs []any
for f := 0; f < numCols; f++ {
v := reflect.ValueOf(e).Field(f).Interface()
vs = append(vs, v)
}
row, err := NewRow(colNames, vs)
if err != nil {
t.Fatal(err)
}
srows = append(srows, row)
}
return &mockIteratorImpl{rows: srows}
}
type mockIteratorImpl struct {
rows []*Row
}
func (i *mockIteratorImpl) Next() (*Row, error) {
if len(i.rows) == 0 {
return nil, iterator.Done
}
row := i.rows[0]
i.rows = i.rows[1:]
return row, nil
}
func (i *mockIteratorImpl) Stop() {
i.rows = nil
}
func (i *mockIteratorImpl) Do(f func(*Row) error) error {
defer i.Stop()
for _, row := range i.rows {
err := f(row)
if err != nil {
return err
}
}
return nil
}