1
0
mirror of https://github.com/labstack/echo.git synced 2024-12-26 20:54:00 +02:00

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
This commit is contained in:
Eugene 2020-01-08 23:40:52 +02:00 committed by Vishal Rana
parent 94d9e009d8
commit 399da56370
2 changed files with 72 additions and 35 deletions

42
bind.go
View File

@ -115,7 +115,7 @@ func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag
if inputFieldName == "" { if inputFieldName == "" {
inputFieldName = typeField.Name inputFieldName = typeField.Name
// If tag is nil, we inspect if the field is a struct. // If tag is nil, we inspect if the field is a struct.
if _, ok := bindUnmarshaler(structField); !ok && structFieldKind == reflect.Struct { if _, ok := structField.Addr().Interface().(BindUnmarshaler); !ok && structFieldKind == reflect.Struct {
if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil { if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil {
return err return err
} }
@ -129,9 +129,8 @@ func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag
// url params are bound case sensitive which is inconsistent. To // url params are bound case sensitive which is inconsistent. To
// fix this we must check all of the map values in a // fix this we must check all of the map values in a
// case-insensitive search. // case-insensitive search.
inputFieldName = strings.ToLower(inputFieldName)
for k, v := range data { for k, v := range data {
if strings.ToLower(k) == inputFieldName { if strings.EqualFold(k, inputFieldName) {
inputValue = v inputValue = v
exists = true exists = true
break break
@ -221,40 +220,13 @@ func unmarshalField(valueKind reflect.Kind, val string, field reflect.Value) (bo
} }
} }
// bindUnmarshaler attempts to unmarshal a reflect.Value into a BindUnmarshaler
func bindUnmarshaler(field reflect.Value) (BindUnmarshaler, bool) {
ptr := reflect.New(field.Type())
if ptr.CanInterface() {
iface := ptr.Interface()
if unmarshaler, ok := iface.(BindUnmarshaler); ok {
return unmarshaler, ok
}
}
return nil, false
}
// textUnmarshaler attempts to unmarshal a reflect.Value into a TextUnmarshaler
func textUnmarshaler(field reflect.Value) (encoding.TextUnmarshaler, bool) {
ptr := reflect.New(field.Type())
if ptr.CanInterface() {
iface := ptr.Interface()
if unmarshaler, ok := iface.(encoding.TextUnmarshaler); ok {
return unmarshaler, ok
}
}
return nil, false
}
func unmarshalFieldNonPtr(value string, field reflect.Value) (bool, error) { func unmarshalFieldNonPtr(value string, field reflect.Value) (bool, error) {
if unmarshaler, ok := bindUnmarshaler(field); ok { fieldIValue := field.Addr().Interface()
err := unmarshaler.UnmarshalParam(value) if unmarshaler, ok := fieldIValue.(BindUnmarshaler); ok {
field.Set(reflect.ValueOf(unmarshaler).Elem()) return true, unmarshaler.UnmarshalParam(value)
return true, err
} }
if unmarshaler, ok := textUnmarshaler(field); ok { if unmarshaler, ok := fieldIValue.(encoding.TextUnmarshaler); ok {
err := unmarshaler.UnmarshalText([]byte(value)) return true, unmarshaler.UnmarshalText([]byte(value))
field.Set(reflect.ValueOf(unmarshaler).Elem())
return true, err
} }
return false, nil return false, nil

View File

@ -56,6 +56,43 @@ type (
Tptr *Timestamp Tptr *Timestamp
SA StringArray SA StringArray
} }
bindTestStructWithTags struct {
I int `json:"I" form:"I"`
PtrI *int `json:"PtrI" form:"PtrI"`
I8 int8 `json:"I8" form:"I8"`
PtrI8 *int8 `json:"PtrI8" form:"PtrI8"`
I16 int16 `json:"I16" form:"I16"`
PtrI16 *int16 `json:"PtrI16" form:"PtrI16"`
I32 int32 `json:"I32" form:"I32"`
PtrI32 *int32 `json:"PtrI32" form:"PtrI32"`
I64 int64 `json:"I64" form:"I64"`
PtrI64 *int64 `json:"PtrI64" form:"PtrI64"`
UI uint `json:"UI" form:"UI"`
PtrUI *uint `json:"PtrUI" form:"PtrUI"`
UI8 uint8 `json:"UI8" form:"UI8"`
PtrUI8 *uint8 `json:"PtrUI8" form:"PtrUI8"`
UI16 uint16 `json:"UI16" form:"UI16"`
PtrUI16 *uint16 `json:"PtrUI16" form:"PtrUI16"`
UI32 uint32 `json:"UI32" form:"UI32"`
PtrUI32 *uint32 `json:"PtrUI32" form:"PtrUI32"`
UI64 uint64 `json:"UI64" form:"UI64"`
PtrUI64 *uint64 `json:"PtrUI64" form:"PtrUI64"`
B bool `json:"B" form:"B"`
PtrB *bool `json:"PtrB" form:"PtrB"`
F32 float32 `json:"F32" form:"F32"`
PtrF32 *float32 `json:"PtrF32" form:"PtrF32"`
F64 float64 `json:"F64" form:"F64"`
PtrF64 *float64 `json:"PtrF64" form:"PtrF64"`
S string `json:"S" form:"S"`
PtrS *string `json:"PtrS" form:"PtrS"`
cantSet string
DoesntExist string `json:"DoesntExist" form:"DoesntExist"`
GoT time.Time `json:"GoT" form:"GoT"`
GoTptr *time.Time `json:"GoTptr" form:"GoTptr"`
T Timestamp `json:"T" form:"T"`
Tptr *Timestamp `json:"Tptr" form:"Tptr"`
SA StringArray `json:"SA" form:"SA"`
}
Timestamp time.Time Timestamp time.Time
TA []Timestamp TA []Timestamp
StringArray []string StringArray []string
@ -433,6 +470,34 @@ func TestBindSetFields(t *testing.T) {
} }
} }
func BenchmarkBindbindData(b *testing.B) {
b.ReportAllocs()
assert := assert.New(b)
ts := new(bindTestStruct)
binder := new(DefaultBinder)
var err error
b.ResetTimer()
for i := 0; i < b.N; i++ {
err = binder.bindData(ts, values, "form")
}
assert.NoError(err)
assertBindTestStruct(assert, ts)
}
func BenchmarkBindbindDataWithTags(b *testing.B) {
b.ReportAllocs()
assert := assert.New(b)
ts := new(bindTestStructWithTags)
binder := new(DefaultBinder)
var err error
b.ResetTimer()
for i := 0; i < b.N; i++ {
err = binder.bindData(ts, values, "form")
}
assert.NoError(err)
assertBindTestStruct(assert, (*bindTestStruct)(ts))
}
func assertBindTestStruct(a *assert.Assertions, ts *bindTestStruct) { func assertBindTestStruct(a *assert.Assertions, ts *bindTestStruct) {
a.Equal(0, ts.I) a.Equal(0, ts.I)
a.Equal(int8(8), ts.I8) a.Equal(int8(8), ts.I8)