Skip to content

Commit 399da56

Browse files
ffenix113vishr
authored andcommitted
Improve bind performance (#1469)
* Improve bind performance By some slight optimisations and lesser reflect usage now binding has significantly better performance: name old time/op new time/op delta BindbindData-8 21.2µs ± 2% 13.5µs ± 2% -36.66% (p=0.000 n=16+18) BindbindDataWithTags-8 22.1µs ± 1% 16.4µs ± 2% -26.03% (p=0.000 n=20+20) name old alloc/op new alloc/op delta BindbindData-8 2.40kB ± 0% 1.33kB ± 0% -44.64% (p=0.000 n=20+20) BindbindDataWithTags-8 2.31kB ± 0% 1.54kB ± 0% -33.19% (p=0.000 n=20+20) name old allocs/op new allocs/op delta BindbindData-8 297 ± 0% 122 ± 0% -58.92% (p=0.000 n=20+20) BindbindDataWithTags-8 267 ± 0% 125 ± 0% -53.18% (p=0.000 n=20+20) * Remove creation of new value in unmarshalFieldNonPtr
1 parent 94d9e00 commit 399da56

File tree

2 files changed

+72
-35
lines changed

2 files changed

+72
-35
lines changed

bind.go

Lines changed: 7 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag
115115
if inputFieldName == "" {
116116
inputFieldName = typeField.Name
117117
// If tag is nil, we inspect if the field is a struct.
118-
if _, ok := bindUnmarshaler(structField); !ok && structFieldKind == reflect.Struct {
118+
if _, ok := structField.Addr().Interface().(BindUnmarshaler); !ok && structFieldKind == reflect.Struct {
119119
if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil {
120120
return err
121121
}
@@ -129,9 +129,8 @@ func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag
129129
// url params are bound case sensitive which is inconsistent. To
130130
// fix this we must check all of the map values in a
131131
// case-insensitive search.
132-
inputFieldName = strings.ToLower(inputFieldName)
133132
for k, v := range data {
134-
if strings.ToLower(k) == inputFieldName {
133+
if strings.EqualFold(k, inputFieldName) {
135134
inputValue = v
136135
exists = true
137136
break
@@ -221,40 +220,13 @@ func unmarshalField(valueKind reflect.Kind, val string, field reflect.Value) (bo
221220
}
222221
}
223222

224-
// bindUnmarshaler attempts to unmarshal a reflect.Value into a BindUnmarshaler
225-
func bindUnmarshaler(field reflect.Value) (BindUnmarshaler, bool) {
226-
ptr := reflect.New(field.Type())
227-
if ptr.CanInterface() {
228-
iface := ptr.Interface()
229-
if unmarshaler, ok := iface.(BindUnmarshaler); ok {
230-
return unmarshaler, ok
231-
}
232-
}
233-
return nil, false
234-
}
235-
236-
// textUnmarshaler attempts to unmarshal a reflect.Value into a TextUnmarshaler
237-
func textUnmarshaler(field reflect.Value) (encoding.TextUnmarshaler, bool) {
238-
ptr := reflect.New(field.Type())
239-
if ptr.CanInterface() {
240-
iface := ptr.Interface()
241-
if unmarshaler, ok := iface.(encoding.TextUnmarshaler); ok {
242-
return unmarshaler, ok
243-
}
244-
}
245-
return nil, false
246-
}
247-
248223
func unmarshalFieldNonPtr(value string, field reflect.Value) (bool, error) {
249-
if unmarshaler, ok := bindUnmarshaler(field); ok {
250-
err := unmarshaler.UnmarshalParam(value)
251-
field.Set(reflect.ValueOf(unmarshaler).Elem())
252-
return true, err
224+
fieldIValue := field.Addr().Interface()
225+
if unmarshaler, ok := fieldIValue.(BindUnmarshaler); ok {
226+
return true, unmarshaler.UnmarshalParam(value)
253227
}
254-
if unmarshaler, ok := textUnmarshaler(field); ok {
255-
err := unmarshaler.UnmarshalText([]byte(value))
256-
field.Set(reflect.ValueOf(unmarshaler).Elem())
257-
return true, err
228+
if unmarshaler, ok := fieldIValue.(encoding.TextUnmarshaler); ok {
229+
return true, unmarshaler.UnmarshalText([]byte(value))
258230
}
259231

260232
return false, nil

bind_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,43 @@ type (
5656
Tptr *Timestamp
5757
SA StringArray
5858
}
59+
bindTestStructWithTags struct {
60+
I int `json:"I" form:"I"`
61+
PtrI *int `json:"PtrI" form:"PtrI"`
62+
I8 int8 `json:"I8" form:"I8"`
63+
PtrI8 *int8 `json:"PtrI8" form:"PtrI8"`
64+
I16 int16 `json:"I16" form:"I16"`
65+
PtrI16 *int16 `json:"PtrI16" form:"PtrI16"`
66+
I32 int32 `json:"I32" form:"I32"`
67+
PtrI32 *int32 `json:"PtrI32" form:"PtrI32"`
68+
I64 int64 `json:"I64" form:"I64"`
69+
PtrI64 *int64 `json:"PtrI64" form:"PtrI64"`
70+
UI uint `json:"UI" form:"UI"`
71+
PtrUI *uint `json:"PtrUI" form:"PtrUI"`
72+
UI8 uint8 `json:"UI8" form:"UI8"`
73+
PtrUI8 *uint8 `json:"PtrUI8" form:"PtrUI8"`
74+
UI16 uint16 `json:"UI16" form:"UI16"`
75+
PtrUI16 *uint16 `json:"PtrUI16" form:"PtrUI16"`
76+
UI32 uint32 `json:"UI32" form:"UI32"`
77+
PtrUI32 *uint32 `json:"PtrUI32" form:"PtrUI32"`
78+
UI64 uint64 `json:"UI64" form:"UI64"`
79+
PtrUI64 *uint64 `json:"PtrUI64" form:"PtrUI64"`
80+
B bool `json:"B" form:"B"`
81+
PtrB *bool `json:"PtrB" form:"PtrB"`
82+
F32 float32 `json:"F32" form:"F32"`
83+
PtrF32 *float32 `json:"PtrF32" form:"PtrF32"`
84+
F64 float64 `json:"F64" form:"F64"`
85+
PtrF64 *float64 `json:"PtrF64" form:"PtrF64"`
86+
S string `json:"S" form:"S"`
87+
PtrS *string `json:"PtrS" form:"PtrS"`
88+
cantSet string
89+
DoesntExist string `json:"DoesntExist" form:"DoesntExist"`
90+
GoT time.Time `json:"GoT" form:"GoT"`
91+
GoTptr *time.Time `json:"GoTptr" form:"GoTptr"`
92+
T Timestamp `json:"T" form:"T"`
93+
Tptr *Timestamp `json:"Tptr" form:"Tptr"`
94+
SA StringArray `json:"SA" form:"SA"`
95+
}
5996
Timestamp time.Time
6097
TA []Timestamp
6198
StringArray []string
@@ -433,6 +470,34 @@ func TestBindSetFields(t *testing.T) {
433470
}
434471
}
435472

473+
func BenchmarkBindbindData(b *testing.B) {
474+
b.ReportAllocs()
475+
assert := assert.New(b)
476+
ts := new(bindTestStruct)
477+
binder := new(DefaultBinder)
478+
var err error
479+
b.ResetTimer()
480+
for i := 0; i < b.N; i++ {
481+
err = binder.bindData(ts, values, "form")
482+
}
483+
assert.NoError(err)
484+
assertBindTestStruct(assert, ts)
485+
}
486+
487+
func BenchmarkBindbindDataWithTags(b *testing.B) {
488+
b.ReportAllocs()
489+
assert := assert.New(b)
490+
ts := new(bindTestStructWithTags)
491+
binder := new(DefaultBinder)
492+
var err error
493+
b.ResetTimer()
494+
for i := 0; i < b.N; i++ {
495+
err = binder.bindData(ts, values, "form")
496+
}
497+
assert.NoError(err)
498+
assertBindTestStruct(assert, (*bindTestStruct)(ts))
499+
}
500+
436501
func assertBindTestStruct(a *assert.Assertions, ts *bindTestStruct) {
437502
a.Equal(0, ts.I)
438503
a.Equal(int8(8), ts.I8)

0 commit comments

Comments
 (0)