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:
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 == "" {
|
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
|
||||||
|
65
bind_test.go
65
bind_test.go
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user