Skip to content

Commit

Permalink
Merge pull request #45 from crast/cached-findfield
Browse files Browse the repository at this point in the history
Optimize findField operation heavily using a type map
  • Loading branch information
serejja committed Sep 4, 2015
2 parents 8f2aba7 + f0b65a0 commit bc650d5
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 21 deletions.
57 changes: 57 additions & 0 deletions datum_reader_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package avro

import (
"bytes"
"fmt"
"testing"
)
Expand Down Expand Up @@ -266,3 +267,59 @@ func TestComplexOfComplexBinding(t *testing.T) {
}
}
}

func BenchmarkSpecificDatumReader_complex(b *testing.B) {
var dest complex
specificReaderBenchComplex(b, &dest)
}

func BenchmarkSpecificDatumReader_hugeval(b *testing.B) {
var dest struct {
complex
primitive
testRecord
}
specificReaderBenchComplex(b, &dest)
}

func specificReaderComplexVal() (Schema, []byte) {
schema, err := ParseSchemaFile("test/schemas/test_record.avsc")
if err != nil {
panic(err)
}
e := NewGenericEnum([]string{"A", "B", "C", "D"})
e.Set("A")
c := newComplex()
c.EnumField.Set("A")
c.FixedField = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
buf := testEncodeBytes(schema, c)
return schema, buf
}

func specificReaderBenchComplex(b *testing.B, dest interface{}) {
schema, buf := specificReaderComplexVal()
datumReader := NewSpecificDatumReader()
datumReader.SetSchema(schema)

b.ResetTimer()

for i := 0; i < b.N; i++ {
dec := NewBinaryDecoder(buf)
err := datumReader.Read(dest, dec)
if err != nil {
b.Fatal(err)
}
}
}

func testEncodeBytes(schema Schema, rec interface{}) []byte {
var buf bytes.Buffer
w := NewSpecificDatumWriter()
w.SetSchema(schema)
encoder := NewBinaryEncoder(&buf)
err := w.Write(rec, encoder)
if err != nil {
panic(err)
}
return buf.Bytes()
}
57 changes: 36 additions & 21 deletions datum_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,35 +21,50 @@ import (
"strings"
)

func fieldByTag(where reflect.Value, name string) reflect.Value {
elemType := where.Type()

for i := 0; i < where.NumField(); i++ {
field := where.Field(i)
if elemType.Field(i).Tag.Get("avro") == name {
return field
}
}

return reflect.Value{}
}

func findField(where reflect.Value, name string) (reflect.Value, error) {
if where.Kind() == reflect.Ptr {
where = where.Elem()
}
t := where.Type()
rm := reflectMap[t]
if rm == nil {
rm = reflectBuildRi(t)
}
if rf, ok := rm.names[name]; ok {
return where.FieldByIndex(rf), nil
}
return reflect.Value{}, fmt.Errorf("Field %s does not exist in %s", name, t.Name())
}

field := fieldByTag(where, name)
if !field.IsValid() {
field = where.FieldByName(strings.ToUpper(name[0:1]) + name[1:])
if !field.IsValid() {
field = where.FieldByName(name)
func reflectBuildRi(t reflect.Type) *reflectInfo {
rm := &reflectInfo{
names: make(map[string][]int),
}
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
if strings.ToLower(f.Name[:1]) != f.Name[:1] {
tag := f.Tag.Get("avro")
if tag != "" {
rm.names[tag] = f.Index
} else {
rm.names[f.Name] = f.Index
rm.names[strings.ToLower(f.Name[:1])+f.Name[1:]] = f.Index
}
}
}

if !field.IsValid() {
return reflect.Value{}, fmt.Errorf("Field %s does not exist", name)
// copy the map instead of dealing with locking
m := make(map[reflect.Type]*reflectInfo, len(reflectMap)+1)
for k, v := range reflectMap {
m[k] = v
}
m[t] = rm
reflectMap = m
return rm
}

var reflectMap map[reflect.Type]*reflectInfo

return field, nil
type reflectInfo struct {
names map[string][]int
}

0 comments on commit bc650d5

Please sign in to comment.