mirror of
https://github.com/labstack/echo.git
synced 2024-12-24 20:14:31 +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:
parent
94d9e009d8
commit
399da56370
42
bind.go
42
bind.go
@ -115,7 +115,7 @@ func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag
|
||||
if inputFieldName == "" {
|
||||
inputFieldName = typeField.Name
|
||||
// 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 {
|
||||
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
|
||||
// fix this we must check all of the map values in a
|
||||
// case-insensitive search.
|
||||
inputFieldName = strings.ToLower(inputFieldName)
|
||||
for k, v := range data {
|
||||
if strings.ToLower(k) == inputFieldName {
|
||||
if strings.EqualFold(k, inputFieldName) {
|
||||
inputValue = v
|
||||
exists = true
|
||||
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) {
|
||||
if unmarshaler, ok := bindUnmarshaler(field); ok {
|
||||
err := unmarshaler.UnmarshalParam(value)
|
||||
field.Set(reflect.ValueOf(unmarshaler).Elem())
|
||||
return true, err
|
||||
fieldIValue := field.Addr().Interface()
|
||||
if unmarshaler, ok := fieldIValue.(BindUnmarshaler); ok {
|
||||
return true, unmarshaler.UnmarshalParam(value)
|
||||
}
|
||||
if unmarshaler, ok := textUnmarshaler(field); ok {
|
||||
err := unmarshaler.UnmarshalText([]byte(value))
|
||||
field.Set(reflect.ValueOf(unmarshaler).Elem())
|
||||
return true, err
|
||||
if unmarshaler, ok := fieldIValue.(encoding.TextUnmarshaler); ok {
|
||||
return true, unmarshaler.UnmarshalText([]byte(value))
|
||||
}
|
||||
|
||||
return false, nil
|
||||
|
65
bind_test.go
65
bind_test.go
@ -56,6 +56,43 @@ type (
|
||||
Tptr *Timestamp
|
||||
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
|
||||
TA []Timestamp
|
||||
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) {
|
||||
a.Equal(0, ts.I)
|
||||
a.Equal(int8(8), ts.I8)
|
||||
|
Loading…
Reference in New Issue
Block a user