Skip to content

Commit

Permalink
Try to avoid reflection in packer as much as possible
Browse files Browse the repository at this point in the history
  • Loading branch information
khaf committed Apr 24, 2017
1 parent a6ba8d0 commit 71c5889
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 56 deletions.
43 changes: 3 additions & 40 deletions bench_packing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@ func Benchmark_Pack_binary_PutUint64(b *testing.B) {

func doPack(val interface{}, b *testing.B) {
var err error
v := NewValue(val)
runtime.GC()
b.ResetTimer()
for i := 0; i < b.N; i++ {
buf.dataOffset = 0
v := NewValue(val)
_, err = v.pack(buf)
if err != nil {
panic(err)
Expand All @@ -62,73 +64,46 @@ func doPack(val interface{}, b *testing.B) {

func Benchmark_Pack_________Int64(b *testing.B) {
val := rand.Int63()
b.N = 1000
runtime.GC()
b.ResetTimer()
doPack(val, b)
}

func Benchmark_Pack_________Int32(b *testing.B) {
val := rand.Int31()
b.N = 1000
runtime.GC()
b.ResetTimer()
doPack(val, b)
}

func Benchmark_Pack_String______1(b *testing.B) {
val := strings.Repeat("s", 1)
b.N = 1000
runtime.GC()
b.ResetTimer()
doPack(val, b)
}

func Benchmark_Pack_String_____10(b *testing.B) {
val := strings.Repeat("s", 10)
b.N = 1000
runtime.GC()
b.ResetTimer()
doPack(val, b)
}

func Benchmark_Pack_String____100(b *testing.B) {
val := strings.Repeat("s", 100)
b.N = 1000
runtime.GC()
b.ResetTimer()
doPack(val, b)
}

func Benchmark_Pack_String___1000(b *testing.B) {
val := strings.Repeat("s", 1000)
b.N = 1000
runtime.GC()
b.ResetTimer()
doPack(val, b)
}

func Benchmark_Pack_String__10000(b *testing.B) {
val := strings.Repeat("s", 10000)
b.N = 1000
runtime.GC()
b.ResetTimer()
doPack(val, b)
}

func Benchmark_Pack_String_100000(b *testing.B) {
val := strings.Repeat("s", 100000)
b.N = 1000
runtime.GC()
b.ResetTimer()
doPack(val, b)
}

func Benchmark_Pack_Complex_IfcArray_Direct(b *testing.B) {
val := []interface{}{1, 1, 1, "a simple string", nil, rand.Int63(), []byte{12, 198, 211}}
b.N = 1000
runtime.GC()
b.ResetTimer()
doPack(val, b)
}

Expand All @@ -155,17 +130,11 @@ func (m myList) Len() int {

func Benchmark_Pack_Complex_Array_ListIter(b *testing.B) {
val := myList([]string{strings.Repeat("s", 1), strings.Repeat("s", 2), strings.Repeat("s", 3), strings.Repeat("s", 4), strings.Repeat("s", 5), strings.Repeat("s", 6), strings.Repeat("s", 7), strings.Repeat("s", 8), strings.Repeat("s", 9), strings.Repeat("s", 10)})
b.N = 1000
runtime.GC()
b.ResetTimer()
doPack(val, b)
}

func Benchmark_Pack_Complex_ValueArray(b *testing.B) {
val := []Value{NewValue(1), NewValue(strings.Repeat("s", 100000)), NewValue(1.75), NewValue(nil)}
b.N = 1000
runtime.GC()
b.ResetTimer()
doPack(val, b)
}

Expand All @@ -177,9 +146,6 @@ func Benchmark_Pack_Complex_Map(b *testing.B) {
15892987: strings.Repeat("s", 100),
"s2": []interface{}{"a simple string", nil, rand.Int63(), []byte{12, 198, 211}},
}
b.N = 1000
runtime.GC()
b.ResetTimer()
doPack(val, b)
}

Expand All @@ -191,9 +157,6 @@ func Benchmark_Pack_Complex_JsonMap(b *testing.B) {
"15892987": strings.Repeat("s", 100),
"s2": []interface{}{"a simple string", nil, rand.Int63(), []byte{12, 198, 211}},
}
b.N = 1000
runtime.GC()
b.ResetTimer()
doPack(val, b)
}

Expand Down
8 changes: 8 additions & 0 deletions packer.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ func __PackObject(cmd BufferEx, obj interface{}, mapKey bool) (int, error) {
switch v := obj.(type) {
case Value:
return v.pack(cmd)
case []Value:
return ValueArray(v).pack(cmd)
case string:
return __PackString(cmd, v)
case []byte:
Expand Down Expand Up @@ -281,6 +283,12 @@ func __PackObject(cmd BufferEx, obj interface{}, mapKey bool) (int, error) {
return __PackMap(cmd, obj.(MapIter))
}

// try to see if the object is convertible to a concrete value.
// This will be faster and much more memory efficient than reflection.
if v := tryConcreteValue(obj); v != nil {
return v.pack(cmd)
}

if __packObjectReflect != nil {
return __packObjectReflect(cmd, obj, mapKey)
}
Expand Down
42 changes: 26 additions & 16 deletions value.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,22 +61,9 @@ type AerospikeBlob interface {
EncodeBlob() ([]byte, error)
}

// NewValue generates a new Value object based on the type.
// If the type is not supported, NewValue will panic.
// This method is a convenience method, and should not be used
// when absolute performance is required unless for the reason mentioned below.
//
// If you have custom maps or slices like:
// type MyMap map[primitive1]primitive2, eg: map[int]string
// or
// type MySlice []primitive, eg: []float64
// cast them to their primitive type when passing them to this method:
// v := NewValue(map[int]string(myVar))
// v := NewValue([]float64(myVar))
// This way you will avoid hitting reflection.
// To completely avoid reflection in the library,
// use the build tag: as_performance while building your program.
func NewValue(v interface{}) Value {
// tryConcreteValue will return an aerospike value.
// If the encoder does not exists, it will not try to use reflection.
func tryConcreteValue(v interface{}) Value {
switch val := v.(type) {
case Value:
return val
Expand Down Expand Up @@ -418,6 +405,29 @@ func NewValue(v interface{}) Value {
return NewMapperValue(uint64InterfaceMap(val))
}

return nil
}

// NewValue generates a new Value object based on the type.
// If the type is not supported, NewValue will panic.
// This method is a convenience method, and should not be used
// when absolute performance is required unless for the reason mentioned below.
//
// If you have custom maps or slices like:
// type MyMap map[primitive1]primitive2, eg: map[int]string
// or
// type MySlice []primitive, eg: []float64
// cast them to their primitive type when passing them to this method:
// v := NewValue(map[int]string(myVar))
// v := NewValue([]float64(myVar))
// This way you will avoid hitting reflection.
// To completely avoid reflection in the library,
// use the build tag: as_performance while building your program.
func NewValue(v interface{}) Value {
if value := tryConcreteValue(v); value != nil {
return value
}

if newValueReflect != nil {
if res := newValueReflect(v); res != nil {
return res
Expand Down

0 comments on commit 71c5889

Please sign in to comment.