-
Notifications
You must be signed in to change notification settings - Fork 1.5k
spanner: (*Row).ToStruct is 3x slower than (*Row).Columns #9111
Copy link
Copy link
Closed
Labels
api: spannerIssues related to the Spanner API.Issues related to the Spanner API.priority: p1Important issue which blocks shipping the next release. Will be fixed prior to next release.Important issue which blocks shipping the next release. Will be fixed prior to next release.
Description
In local benchmarks (*Row).ToStruct is >3x slower than (*Row).Columns.
goos: darwin
goarch: amd64
pkg: github.com/kouzoh/go-microservices-kit/database/spanner
cpu: Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
│ baseline-columns │ baseline-tostruct │
│ sec/op │ sec/op vs base │
1 211.1n ± 1% 657.9n ± 7% +211.70% (p=0.000 n=10)
10 2.130µ ± 1% 6.107µ ± 4% +186.71% (p=0.000 n=10)
100 15.74µ ± 1% 54.36µ ± 2% +245.33% (p=0.000 n=10)
1000 157.7µ ± 1% 538.0µ ± 2% +241.14% (p=0.000 n=10)
10000 1.753m ± 8% 5.591m ± 11% +218.86% (p=0.000 n=10)
geomean 18.13µ 58.01µ +220.03%
│ baseline-columns │ baseline-tostruct │
│ B/op │ B/op vs base │
1 48.00 ± 0% 128.00 ± 0% +166.67% (p=0.000 n=10)
10 984.0 ± 0% 1568.0 ± 0% +59.35% (p=0.000 n=10)
100 8.320Ki ± 0% 13.812Ki ± 0% +66.01% (p=0.000 n=10)
1000 99.41Ki ± 0% 154.12Ki ± 0% +55.03% (p=0.000 n=10)
10000 1.287Mi ± 0% 1.822Mi ± 0% +41.48% (p=0.000 n=10)
geomean 8.675Ki 15.00Ki +72.95%
│ baseline-columns │ baseline-tostruct │
│ allocs/op │ allocs/op vs base │
1 2.000 ± 0% 5.000 ± 0% +150.00% (p=0.000 n=10)
10 15.00 ± 0% 36.00 ± 0% +140.00% (p=0.000 n=10)
100 108.0 ± 0% 309.0 ± 0% +186.11% (p=0.000 n=10)
1000 1.012k ± 0% 3.013k ± 0% +197.73% (p=0.000 n=10)
10000 10.02k ± 0% 30.02k ± 0% +199.63% (p=0.000 n=10)
geomean 126.9 347.0 +173.54%
Some overhead is understandable, but 3x starts to matter on hot paths.
Not sure about what should be done to improve things, short of the obvious ones:
- do validation work common to a specific triple of (resultset schema, destination struct, lenient) only once the first time that triple is encountered, and memorize the result (including the mapping between source and destination fields) for subsequent reuse
- otherwise minimize allocations
From a cursory look at decodeStruct it seems like doing the first one should roughly cut the time spent in that function by 40-50%.
cloud.google.com/go/spanner.decodeStruct
/Users/cafxx/go/pkg/mod/cloud.google.com/go/spanner@v1.53.1/value.go
Total: 1.02s 6.49s (flat, cum) 16.06%
3197 . . }
3198 . .
3199 . . // decodeStruct decodes proto3.ListValue pb into struct referenced by pointer
3200 . . // ptr, according to
3201 . . // the structural information given in sppb.StructType ty.
3202 50ms 50ms func decodeStruct(ty *sppb.StructType, pb *proto3.ListValue, ptr interface{}, lenient bool) error {
3203 100ms 100ms if reflect.ValueOf(ptr).IsNil() {
3204 . . return errNilDst(ptr)
3205 . . }
3206 . . if ty == nil {
3207 . . return errNilSpannerStructType()
3208 . . }
3209 . . // t holds the structural information of ptr.
3210 . 110ms t := reflect.TypeOf(ptr).Elem()
3211 . . // v is the actual value that ptr points to.
3212 120ms 180ms v := reflect.ValueOf(ptr).Elem()
3213 . .
3214 10ms 930ms fields, err := fieldCache.Fields(t)
3215 10ms 10ms if err != nil {
3216 . . return ToSpannerError(err)
3217 . . }
3218 . . // return error if lenient is true and destination has duplicate exported columns
3219 . . if lenient {
3220 10ms 10ms fieldNames := getAllFieldNames(v)
3221 . . for _, f := range fieldNames {
3222 . . if fields.Match(f) == nil {
3223 . . return errDupGoField(ptr, f)
3224 . . }
3225 . . }
3226 . . }
3227 10ms 90ms seen := map[string]bool{}
3228 90ms 90ms for i, f := range ty.Fields {
3229 140ms 140ms if f.Name == "" {
3230 . . return errUnnamedField(ty, i)
3231 . . }
3232 80ms 1.38s sf := fields.Match(f.Name)
3233 10ms 10ms if sf == nil {
3234 . . if lenient {
3235 . . continue
3236 . . }
3237 . . return errNoOrDupGoField(ptr, f.Name)
3238 . . }
3239 70ms 200ms if seen[f.Name] {
3240 . . // We don't allow duplicated field name.
3241 . . return errDupSpannerField(f.Name, ty)
3242 . . }
3243 30ms 30ms opts := []decodeOptions{withLenient{lenient: lenient}}
3244 . . // Try to decode a single field.
3245 190ms 2.17s if err := decodeValue(pb.Values[i], f.Type, v.FieldByIndex(sf.Index).Addr().Interface(), opts...); err != nil {
3246 . . return errDecodeStructField(ty, f.Name, err)
3247 . . }
3248 . . // Mark field f.Name as processed.
3249 80ms 970ms seen[f.Name] = true
3250 . . }
3251 20ms 20ms return nil
3252 . . }
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
api: spannerIssues related to the Spanner API.Issues related to the Spanner API.priority: p1Important issue which blocks shipping the next release. Will be fixed prior to next release.Important issue which blocks shipping the next release. Will be fixed prior to next release.
