Skip to content

Commit 34d2f9c

Browse files
authored
PERF: cache NameMapping (#135)
1 parent 531311d commit 34d2f9c

File tree

6 files changed

+102
-49
lines changed

6 files changed

+102
-49
lines changed

README.md

+16-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,21 @@
77

88
gocraft/dbr provides additions to Go's database/sql for super fast performance and convenience.
99

10+
## Benchmark (2018-05-11)
11+
12+
```
13+
BenchmarkLoadValues/sqlx_10-8 5000 407318 ns/op 3913 B/op 164 allocs/op
14+
BenchmarkLoadValues/dbr_10-8 5000 372940 ns/op 3874 B/op 123 allocs/op
15+
BenchmarkLoadValues/sqlx_100-8 2000 584197 ns/op 30195 B/op 1428 allocs/op
16+
BenchmarkLoadValues/dbr_100-8 3000 558852 ns/op 22965 B/op 937 allocs/op
17+
BenchmarkLoadValues/sqlx_1000-8 1000 2319101 ns/op 289339 B/op 14031 allocs/op
18+
BenchmarkLoadValues/dbr_1000-8 1000 2310441 ns/op 210092 B/op 9040 allocs/op
19+
BenchmarkLoadValues/sqlx_10000-8 100 17004716 ns/op 3193997 B/op 140043 allocs/op
20+
BenchmarkLoadValues/dbr_10000-8 100 16150062 ns/op 2394698 B/op 90051 allocs/op
21+
BenchmarkLoadValues/sqlx_100000-8 10 170068209 ns/op 31679944 B/op 1400053 allocs/op
22+
BenchmarkLoadValues/dbr_100000-8 10 147202536 ns/op 23680625 B/op 900061 allocs/op
23+
```
24+
1025
## Driver support
1126

1227
* MySQL
@@ -28,4 +43,4 @@ Contributors:
2843

2944

3045
## License
31-
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fgocraft%2Fdbr.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fgocraft%2Fdbr?ref=badge_large)
46+
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fgocraft%2Fdbr.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fgocraft%2Fdbr?ref=badge_large)

insert.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ func (b *InsertStmt) Record(structValue interface{}) *InsertStmt {
152152
if v.Kind() == reflect.Struct {
153153
found := make([]interface{}, len(b.Column)+1)
154154
// ID is recommended by golint here
155-
findValueByName(v, append(b.Column, "id"), found, false)
155+
s := newTagStore()
156+
s.findValueByName(v, append(b.Column, "id"), found, false)
156157

157158
value := found[:len(found)-1]
158159
for i, v := range value {

load.go

+16-29
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ func Load(rows *sql.Rows, value interface{}) (int, error) {
2626
if err != nil {
2727
return 0, err
2828
}
29+
ptr := make([]interface{}, len(column))
2930

3031
v := reflect.ValueOf(value)
3132
if v.Kind() != reflect.Ptr || v.IsNil() {
@@ -39,11 +40,11 @@ func Load(rows *sql.Rows, value interface{}) (int, error) {
3940
if isMap {
4041
v.Set(reflect.MakeMap(v.Type()))
4142
}
43+
44+
s := newTagStore()
4245
count := 0
4346
for rows.Next() {
4447
var elem, keyElem reflect.Value
45-
var ptr []interface{}
46-
var err error
4748

4849
if isMapOfSlices {
4950
elem = reflectAlloc(v.Type().Elem().Elem())
@@ -54,27 +55,36 @@ func Load(rows *sql.Rows, value interface{}) (int, error) {
5455
}
5556

5657
if isMap {
57-
ptr, err = findPtr(column[1:], elem)
58+
err := s.findPtr(elem, column[1:], ptr[1:])
5859
if err != nil {
5960
return 0, err
6061
}
6162
keyElem = reflectAlloc(v.Type().Key())
62-
keyPtr, err := findPtr(column[0:1], keyElem)
63+
err = s.findPtr(keyElem, column[:1], ptr[:1])
6364
if err != nil {
6465
return 0, err
6566
}
66-
ptr = append(keyPtr, ptr...)
6767
} else {
68-
ptr, err = findPtr(column, elem)
68+
err := s.findPtr(elem, column, ptr)
6969
if err != nil {
7070
return 0, err
7171
}
7272
}
7373

74+
// Before scanning, set nil pointer to dummy dest.
75+
// After that, reset pointers to nil for the next batch.
76+
for i := range ptr {
77+
if ptr[i] == nil {
78+
ptr[i] = dummyDest
79+
}
80+
}
7481
err = rows.Scan(ptr...)
7582
if err != nil {
7683
return 0, err
7784
}
85+
for i := range ptr {
86+
ptr[i] = nil
87+
}
7888

7989
count++
8090

@@ -112,26 +122,3 @@ var (
112122
dummyDest sql.Scanner = dummyScanner{}
113123
typeScanner = reflect.TypeOf((*sql.Scanner)(nil)).Elem()
114124
)
115-
116-
func findPtr(column []string, value reflect.Value) ([]interface{}, error) {
117-
if value.CanAddr() && value.Addr().Type().Implements(typeScanner) {
118-
return []interface{}{value.Addr().Interface()}, nil
119-
}
120-
switch value.Kind() {
121-
case reflect.Struct:
122-
ptr := make([]interface{}, len(column))
123-
findValueByName(value, column, ptr, true)
124-
for i := range ptr {
125-
if ptr[i] == nil {
126-
ptr[i] = dummyDest
127-
}
128-
}
129-
return ptr, nil
130-
case reflect.Ptr:
131-
if value.IsNil() {
132-
value.Set(reflect.New(value.Type().Elem()))
133-
}
134-
return findPtr(column, value.Elem())
135-
}
136-
return []interface{}{value.Addr().Interface()}, nil
137-
}

load_benchmark_test.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package dbr
22

33
import (
4+
"context"
45
"fmt"
56
"testing"
67

@@ -53,7 +54,7 @@ func BenchmarkLoadValues(b *testing.B) {
5354
for i := 0; i < b.N; i++ {
5455
var suggs []*Suggestion
5556
b.StartTimer()
56-
err := db.Select(&suggs, query)
57+
err := db.SelectContext(context.Background(), &suggs, query)
5758
b.StopTimer()
5859
require.NoError(b, err)
5960
require.Len(b, suggs, n)
@@ -65,7 +66,7 @@ func BenchmarkLoadValues(b *testing.B) {
6566
for i := 0; i < b.N; i++ {
6667
var suggs []*Suggestion
6768
b.StartTimer()
68-
_, err := sess.SelectBySql(query).Load(&suggs)
69+
_, err := sess.SelectBySql(query).LoadContext(context.Background(), &suggs)
6970
b.StopTimer()
7071
require.NoError(b, err)
7172
require.Len(b, suggs, n)

util.go

+63-15
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,22 @@ var (
4747
typeValuer = reflect.TypeOf((*driver.Valuer)(nil)).Elem()
4848
)
4949

50-
func findValueByName(value reflect.Value, name []string, found []interface{}, retPtr bool) {
51-
if value.Type().Implements(typeValuer) {
52-
return
50+
type tagStore struct {
51+
m map[reflect.Type][]string
52+
}
53+
54+
func newTagStore() *tagStore {
55+
return &tagStore{
56+
m: make(map[reflect.Type][]string),
5357
}
54-
switch value.Kind() {
55-
case reflect.Ptr:
56-
if value.IsNil() {
57-
return
58-
}
59-
findValueByName(value.Elem(), name, found, retPtr)
60-
case reflect.Struct:
61-
t := value.Type()
58+
}
59+
60+
func (s *tagStore) get(t reflect.Type) []string {
61+
if t.Kind() != reflect.Struct {
62+
return nil
63+
}
64+
if _, ok := s.m[t]; !ok {
65+
l := make([]string, t.NumField())
6266
for i := 0; i < t.NumField(); i++ {
6367
field := t.Field(i)
6468
if field.PkgPath != "" && !field.Anonymous {
@@ -74,20 +78,64 @@ func findValueByName(value reflect.Value, name []string, found []interface{}, re
7478
// no tag, but we can record the field name
7579
tag = NameMapping(field.Name)
7680
}
81+
l[i] = tag
82+
}
83+
s.m[t] = l
84+
}
85+
return s.m[t]
86+
}
87+
88+
func (s *tagStore) findPtr(value reflect.Value, name []string, ptr []interface{}) error {
89+
if value.CanAddr() && value.Addr().Type().Implements(typeScanner) {
90+
ptr[0] = value.Addr().Interface()
91+
return nil
92+
}
93+
switch value.Kind() {
94+
case reflect.Struct:
95+
s.findValueByName(value, name, ptr, true)
96+
return nil
97+
case reflect.Ptr:
98+
if value.IsNil() {
99+
value.Set(reflect.New(value.Type().Elem()))
100+
}
101+
return s.findPtr(value.Elem(), name, ptr)
102+
default:
103+
ptr[0] = value.Addr().Interface()
104+
return nil
105+
}
106+
}
107+
108+
func (s *tagStore) findValueByName(value reflect.Value, name []string, ret []interface{}, retPtr bool) {
109+
if value.Type().Implements(typeValuer) {
110+
return
111+
}
112+
switch value.Kind() {
113+
case reflect.Ptr:
114+
if value.IsNil() {
115+
return
116+
}
117+
s.findValueByName(value.Elem(), name, ret, retPtr)
118+
case reflect.Struct:
119+
l := s.get(value.Type())
120+
for i := 0; i < value.NumField(); i++ {
121+
tag := l[i]
122+
if tag == "" {
123+
continue
124+
}
77125
fieldValue := value.Field(i)
78126
for i, want := range name {
79127
if want != tag {
80128
continue
81129
}
82-
if found[i] == nil {
130+
if ret[i] == nil {
83131
if retPtr {
84-
found[i] = fieldValue.Addr().Interface()
132+
ret[i] = fieldValue.Addr().Interface()
85133
} else {
86-
found[i] = fieldValue
134+
ret[i] = fieldValue
87135
}
88136
}
89137
}
90-
findValueByName(fieldValue, name, found, retPtr)
138+
s.findValueByName(fieldValue, name, ret, retPtr)
91139
}
92140
}
93141
}

util_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ func TestFindValueByName(t *testing.T) {
9999
},
100100
} {
101101
found := make([]interface{}, len(test.name))
102-
findValueByName(reflect.ValueOf(test.in), test.name, found, false)
102+
s := newTagStore()
103+
s.findValueByName(reflect.ValueOf(test.in), test.name, found, false)
103104

104105
var got []string
105106
for i, v := range found {

0 commit comments

Comments
 (0)