Skip to content

Commit

Permalink
Add TraversalsByName for zero allocation field traversal
Browse files Browse the repository at this point in the history
  • Loading branch information
nussjustin committed Sep 28, 2017
1 parent d9bd385 commit a2429ba
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 6 deletions.
31 changes: 25 additions & 6 deletions reflectx/reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
69 changes: 69 additions & 0 deletions reflectx/reflect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}

0 comments on commit a2429ba

Please sign in to comment.