This is an automated email from the ASF dual-hosted git repository. wongoo pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/dubbo-go-hessian2.git
The following commit(s) were added to refs/heads/master by this push: new bb036e6 Imp: cache in reflection (#179) bb036e6 is described below commit bb036e6fcafdb3d897fa47615a253f716c8b0d24 Author: huiren <zhr...@gmail.com> AuthorDate: Mon Apr 20 20:54:46 2020 +0800 Imp: cache in reflection (#179) * benchmark result * use cache in findField * encode benchmark * call field() once * remove version * fix import sync * cache in registerPOJO * add json bench result * prune unneccessary rtype.Field(index) * cache comment * rename cache * switch to if * remove return value name * findFieldWithCache * remove if check when fieldStruct is nil Co-authored-by: 望哥 <gelny...@163.com> --- object.go | 82 ++++++++++++++++++++++++++++++++++++++-------------- object_test.go | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ pojo.go | 5 ++++ 3 files changed, 155 insertions(+), 22 deletions(-) diff --git a/object.go b/object.go index 4025dac..d629933 100644 --- a/object.go +++ b/object.go @@ -21,6 +21,7 @@ import ( "io" "reflect" "strings" + "sync" ) import ( @@ -164,21 +165,23 @@ func (e *Encoder) encObject(v POJO) error { structs := []reflect.Value{vv} for len(structs) > 0 { vv := structs[0] + vvt := vv.Type() num = vv.NumField() for i = 0; i < num; i++ { + tf := vvt.Field(i) // skip unexported anonymous field - if vv.Type().Field(i).PkgPath != "" { + if tf.PkgPath != "" { continue } // skip ignored field - if tag, _ := vv.Type().Field(i).Tag.Lookup(tagIdentifier); tag == `-` { + if tag, _ := tf.Tag.Lookup(tagIdentifier); tag == `-` { continue } field := vv.Field(i) - if vv.Type().Field(i).Anonymous && field.Kind() == reflect.Struct { - structs = append(structs, vv.Field(i)) + if tf.Anonymous && field.Kind() == reflect.Struct { + structs = append(structs, field) continue } @@ -292,36 +295,71 @@ func (d *Decoder) decClassDef() (interface{}, error) { return classInfo{javaName: clsName, fieldNameList: fieldList}, nil } -func findField(name string, typ reflect.Type) ([]int, error) { +type fieldInfo struct { + indexes []int + field *reflect.StructField +} + +// map[rType][fieldName]indexes +var fieldIndexCache sync.Map + +func findFieldWithCache(name string, typ reflect.Type) ([]int, *reflect.StructField, error) { + typCache, _ := fieldIndexCache.Load(typ) + if typCache == nil { + typCache = &sync.Map{} + fieldIndexCache.Store(typ, typCache) + } + + iindexes, existCache := typCache.(*sync.Map).Load(name) + if existCache && iindexes != nil { + finfo := iindexes.(*fieldInfo) + var err error + if len(finfo.indexes) == 0 { + err = perrors.Errorf("failed to find field %s", name) + } + return finfo.indexes, finfo.field, err + } + + indexes, field, err := findField(name, typ) + typCache.(*sync.Map).Store(name, &fieldInfo{indexes: indexes, field: field}) + return indexes, field, err +} + +// findField find structField in rType +// +// return +// indexes []int +// field reflect.StructField +// err error +func findField(name string, typ reflect.Type) ([]int, *reflect.StructField, error) { for i := 0; i < typ.NumField(); i++ { // matching tag first, then lowerCamelCase, SameCase, lowerCase typField := typ.Field(i) - if val, has := typField.Tag.Lookup(tagIdentifier); has && strings.Compare(val, name) == 0 { - return []int{i}, nil - } + tagVal, hasTag := typField.Tag.Lookup(tagIdentifier) fieldName := typField.Name - switch { - case strings.Compare(lowerCamelCase(fieldName), name) == 0: - return []int{i}, nil - case strings.Compare(fieldName, name) == 0: - return []int{i}, nil - case strings.Compare(strings.ToLower(fieldName), name) == 0: - return []int{i}, nil + if hasTag && tagVal == name || + fieldName == name || + lowerCamelCase(fieldName) == name || + strings.ToLower(fieldName) == name { + + return []int{i}, &typField, nil } if typField.Anonymous && typField.Type.Kind() == reflect.Struct { - next, _ := findField(name, typField.Type) + next, field, _ := findField(name, typField.Type) if len(next) > 0 { - pos := []int{i} - return append(pos, next...), nil + indexes := []int{i} + indexes = append(indexes, next...) + + return indexes, field, nil } } } - return []int{}, perrors.Errorf("failed to find field %s", name) + return []int{}, nil, perrors.Errorf("failed to find field %s", name) } func (d *Decoder) decInstance(typ reflect.Type, cls classInfo) (interface{}, error) { @@ -337,13 +375,13 @@ func (d *Decoder) decInstance(typ reflect.Type, cls classInfo) (interface{}, err for i := 0; i < len(cls.fieldNameList); i++ { fieldName := cls.fieldNameList[i] - index, err := findField(fieldName, typ) + index, fieldStruct, err := findFieldWithCache(fieldName, typ) if err != nil { return nil, perrors.Errorf("can not find field %s", fieldName) } // skip unexported anonymous field - if vv.Type().FieldByIndex(index).PkgPath != "" { + if fieldStruct.PkgPath != "" { continue } @@ -483,7 +521,7 @@ func (d *Decoder) decInstance(typ reflect.Type, cls classInfo) (interface{}, err } default: - return nil, perrors.Errorf("unknown struct member type: %v %v", kind, typ.Name()+"."+typ.FieldByIndex(index).Name) + return nil, perrors.Errorf("unknown struct member type: %v %v", kind, typ.Name()+"."+fieldStruct.Name) } } // end for diff --git a/object_test.go b/object_test.go index 2484d07..76654c7 100644 --- a/object_test.go +++ b/object_test.go @@ -18,9 +18,11 @@ package hessian import ( + "encoding/json" "math" "reflect" "testing" + "time" ) type Department struct { @@ -659,3 +661,91 @@ func TestIssue150_EmbedStructJavaDecode(t *testing.T) { testJavaDecode(t, "customArgTypedFixed_Extends", dog) } + +type Mix struct { + A int + B string + CA time.Time + CB int64 + CC string + CD []float64 + D map[string]interface{} +} + +func (m Mix) JavaClassName() string { + return `test.mix` +} + +func init() { + RegisterPOJO(new(Mix)) +} + +// +// BenchmarkJsonEncode-8 217354 4799 ns/op 832 B/op 15 allocs/op +func BenchmarkJsonEncode(b *testing.B) { + m := Mix{A: int('a'), B: `hello`} + m.CD = []float64{1, 2, 3} + m.D = map[string]interface{}{`floats`: m.CD, `A`: m.A, `m`: m} + + for i := 0; i < b.N; i++ { + _, err := json.Marshal(&m) + if err != nil { + b.Error(err) + } + } +} + +// +// BenchmarkEncode-8 211452 5560 ns/op 1771 B/op 51 allocs/op +func BenchmarkEncode(b *testing.B) { + m := Mix{A: int('a'), B: `hello`} + m.CD = []float64{1, 2, 3} + m.D = map[string]interface{}{`floats`: m.CD, `A`: m.A, `m`: m} + + for i := 0; i < b.N; i++ { + _, err := encodeTarget(&m) + if err != nil { + b.Error(err) + } + } +} + +// +// BenchmarkJsonDecode-8 123922 8549 ns/op 1776 B/op 51 allocs/op +func BenchmarkJsonDecode(b *testing.B) { + m := Mix{A: int('a'), B: `hello`} + m.CD = []float64{1, 2, 3} + m.D = map[string]interface{}{`floats`: m.CD, `A`: m.A, `m`: m} + bytes, err := json.Marshal(&m) + if err != nil { + b.Error(err) + } + + for i := 0; i < b.N; i++ { + m := &Mix{} + err := json.Unmarshal(bytes, m) + if err != nil { + b.Error(err) + } + } +} + +// +// BenchmarkDecode-8 104196 10924 ns/op 6424 B/op 98 allocs/op +func BenchmarkDecode(b *testing.B) { + m := Mix{A: int('a'), B: `hello`} + m.CD = []float64{1, 2, 3} + m.D = map[string]interface{}{`floats`: m.CD, `A`: m.A, `m`: m} + bytes, err := encodeTarget(&m) + if err != nil { + b.Error(err) + } + + for i := 0; i < b.N; i++ { + d := NewDecoder(bytes) + _, err := d.Decode() + if err != nil { + b.Error(err) + } + } +} diff --git a/pojo.go b/pojo.go index 0c9f9aa..be74bac 100644 --- a/pojo.go +++ b/pojo.go @@ -120,6 +120,11 @@ func RegisterPOJO(o POJO) int { pojoRegistry.Lock() defer pojoRegistry.Unlock() + if goName, ok := pojoRegistry.j2g[o.JavaClassName()]; ok { + return pojoRegistry.registry[goName].index + } + + // JavaClassName shouldn't equal to goName if _, ok := pojoRegistry.registry[o.JavaClassName()]; ok { return -1 }