io: optimize BinReader.ReadArray()
reflect.MethodByName is a rather expensive function especially when called on hot path. This became obvious during profiling of db restore. This commit replaces reflection with a cast to an interface.
This commit is contained in:
parent
e4d821f32d
commit
1784a14148
3 changed files with 18 additions and 33 deletions
|
@ -47,19 +47,14 @@ func (r *BinReader) ReadArray(t interface{}, maxSize ...int) {
|
||||||
panic(value.Type().String() + " is not a pointer to a slice")
|
panic(value.Type().String() + " is not a pointer to a slice")
|
||||||
}
|
}
|
||||||
|
|
||||||
sliceType := value.Elem().Type()
|
|
||||||
elemType := sliceType.Elem()
|
|
||||||
isPtr := elemType.Kind() == reflect.Ptr
|
|
||||||
if isPtr {
|
|
||||||
checkHasDecodeBinary(elemType)
|
|
||||||
} else {
|
|
||||||
checkHasDecodeBinary(reflect.PtrTo(elemType))
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Err != nil {
|
if r.Err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sliceType := value.Elem().Type()
|
||||||
|
elemType := sliceType.Elem()
|
||||||
|
isPtr := elemType.Kind() == reflect.Ptr
|
||||||
|
|
||||||
ms := maxArraySize
|
ms := maxArraySize
|
||||||
if len(maxSize) != 0 {
|
if len(maxSize) != 0 {
|
||||||
ms = maxSize[0]
|
ms = maxSize[0]
|
||||||
|
@ -82,27 +77,18 @@ func (r *BinReader) ReadArray(t interface{}, maxSize ...int) {
|
||||||
} else {
|
} else {
|
||||||
elem = arr.Index(i).Addr()
|
elem = arr.Index(i).Addr()
|
||||||
}
|
}
|
||||||
method := elem.MethodByName("DecodeBinary")
|
|
||||||
method.Call([]reflect.Value{reflect.ValueOf(r)})
|
el, ok := elem.Interface().(decodable)
|
||||||
|
if !ok {
|
||||||
|
panic(elemType.String() + "is not decodable")
|
||||||
|
}
|
||||||
|
|
||||||
|
el.DecodeBinary(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
value.Elem().Set(arr)
|
value.Elem().Set(arr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkHasDecodeBinary(v reflect.Type) {
|
|
||||||
method, ok := v.MethodByName("DecodeBinary")
|
|
||||||
if !ok || !isDecodeBinaryMethod(method) {
|
|
||||||
panic(v.String() + " does not have DecodeBinary(*io.BinReader)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isDecodeBinaryMethod(method reflect.Method) bool {
|
|
||||||
t := method.Type
|
|
||||||
return t != nil &&
|
|
||||||
t.NumIn() == 2 && t.In(1) == reflect.TypeOf((*BinReader)(nil)) &&
|
|
||||||
t.NumOut() == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadBE reads from the underlying io.Reader
|
// ReadBE reads from the underlying io.Reader
|
||||||
// into the interface v in big-endian format.
|
// into the interface v in big-endian format.
|
||||||
func (r *BinReader) ReadBE(v interface{}) {
|
func (r *BinReader) ReadBE(v interface{}) {
|
||||||
|
|
|
@ -326,15 +326,10 @@ func TestBinReader_ReadArray(t *testing.T) {
|
||||||
require.NoError(t, r.Err)
|
require.NoError(t, r.Err)
|
||||||
require.Equal(t, []testSerializable{}, arrVal)
|
require.Equal(t, []testSerializable{}, arrVal)
|
||||||
|
|
||||||
r = NewBinReaderFromBuf([]byte{0})
|
r = NewBinReaderFromBuf([]byte{1})
|
||||||
r.Err = errors.New("error")
|
require.Panics(t, func() { r.ReadArray(&[]int{1}) })
|
||||||
require.Panics(t, func() { r.ReadArray(&[]*int{}) })
|
|
||||||
|
|
||||||
r = NewBinReaderFromBuf([]byte{0})
|
r = NewBinReaderFromBuf([]byte{0})
|
||||||
r.Err = errors.New("error")
|
r.Err = errors.New("error")
|
||||||
require.Panics(t, func() { r.ReadArray(&[]int{}) })
|
require.Panics(t, func() { r.ReadArray(1) })
|
||||||
|
|
||||||
r = NewBinReaderFromBuf([]byte{0})
|
|
||||||
r.Err = errors.New("error")
|
|
||||||
require.Panics(t, func() { r.ReadArray(0) })
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,3 +10,7 @@ type Serializable interface {
|
||||||
DecodeBinary(*BinReader)
|
DecodeBinary(*BinReader)
|
||||||
EncodeBinary(*BinWriter)
|
EncodeBinary(*BinWriter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type decodable interface {
|
||||||
|
DecodeBinary(*BinReader)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue