From a2429bac81bb59fd46d281cdc953ad9304778cff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Justin=20Nu=C3=9F?= Date: Thu, 28 Sep 2017 20:02:11 +0200 Subject: [PATCH 1/2] Add TraversalsByName for zero allocation field traversal --- reflectx/reflect.go | 31 ++++++++++++++---- reflectx/reflect_test.go | 69 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 6 deletions(-) diff --git a/reflectx/reflect.go b/reflectx/reflect.go index f2802b80..73c21eb3 100644 --- a/reflectx/reflect.go +++ b/reflectx/reflect.go @@ -166,20 +166,39 @@ func (m *Mapper) FieldsByName(v reflect.Value, names []string) []reflect.Value { // traversals for each mapped name. Panics if t is not a struct or Indirectable // to a struct. Returns empty int slice for each name not found. func (m *Mapper) TraversalsByName(t reflect.Type, names []string) [][]int { + r := make([][]int, 0, len(names)) + m.TraversalsByNameFunc(t, names, func(_ int, i []int) error { + if i == nil { + r = append(r, []int{}) + } else { + r = append(r, i) + } + + return nil + }) + return r +} + +// TraversalsByNameFunc traverses the mapped names and calls fn with the index of +// each name and the struct traversal represented by that name. Panics if t is not +// a struct or Indirectable to a struct. Returns the first error returned by fn or nil. +func (m *Mapper) TraversalsByNameFunc(t reflect.Type, names []string, fn func(int, []int) error) error { t = Deref(t) mustBe(t, reflect.Struct) tm := m.TypeMap(t) - - r := make([][]int, 0, len(names)) - for _, name := range names { + for i, name := range names { fi, ok := tm.Names[name] if !ok { - r = append(r, []int{}) + if err := fn(i, nil); err != nil { + return err + } } else { - r = append(r, fi.Index) + if err := fn(i, fi.Index); err != nil { + return err + } } } - return r + return nil } // FieldByIndexes returns a value for the field given by the struct traversal diff --git a/reflectx/reflect_test.go b/reflectx/reflect_test.go index b702f9cd..d3879ed9 100644 --- a/reflectx/reflect_test.go +++ b/reflectx/reflect_test.go @@ -903,3 +903,72 @@ func BenchmarkFieldByIndexL4(b *testing.B) { } } } + +func BenchmarkTraversalsByName(b *testing.B) { + type A struct { + Value int + } + + type B struct { + A A + } + + type C struct { + B B + } + + type D struct { + C C + } + + m := NewMapper("") + t := reflect.TypeOf(D{}) + names := []string{"C", "B", "A", "Value"} + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + if l := len(m.TraversalsByName(t, names)); l != len(names) { + b.Errorf("expected %d values, got %d", len(names), l) + } + } +} + +func BenchmarkTraversalsByNameFunc(b *testing.B) { + type A struct { + Z int + } + + type B struct { + A A + } + + type C struct { + B B + } + + type D struct { + C C + } + + m := NewMapper("") + t := reflect.TypeOf(D{}) + names := []string{"C", "B", "A", "Z", "Y"} + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + var l int + + if err := m.TraversalsByNameFunc(t, names, func(_ int, _ []int) error { + l++ + return nil + }); err != nil { + b.Errorf("unexpected error %s", err) + } + + if l != len(names) { + b.Errorf("expected %d values, got %d", len(names), l) + } + } +} \ No newline at end of file From 2824d91409a3923c9d9a26fcf2a5282d6569a0bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Justin=20Nu=C3=9F?= Date: Thu, 28 Sep 2017 20:06:34 +0200 Subject: [PATCH 2/2] Use TraversalsByNameFunc for binding named arguments --- named.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/named.go b/named.go index dd899d35..69eb9549 100644 --- a/named.go +++ b/named.go @@ -163,16 +163,18 @@ func bindArgs(names []string, arg interface{}, m *reflectx.Mapper) ([]interface{ v = v.Elem() } - fields := m.TraversalsByName(v.Type(), names) - for i, t := range fields { + err := m.TraversalsByNameFunc(v.Type(), names, func(i int, t []int) error { if len(t) == 0 { - return arglist, fmt.Errorf("could not find name %s in %#v", names[i], arg) + return fmt.Errorf("could not find name %s in %#v", names[i], arg) } + val := reflectx.FieldByIndexesReadOnly(v, t) arglist = append(arglist, val.Interface()) - } - return arglist, nil + return nil + }) + + return arglist, err } // like bindArgs, but for maps.