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:
Evgenii Stratonikov 2019-12-06 18:13:52 +03:00
parent e4d821f32d
commit 1784a14148
3 changed files with 18 additions and 33 deletions

View file

@ -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{}) {

View file

@ -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) })
} }

View file

@ -10,3 +10,7 @@ type Serializable interface {
DecodeBinary(*BinReader) DecodeBinary(*BinReader)
EncodeBinary(*BinWriter) EncodeBinary(*BinWriter)
} }
type decodable interface {
DecodeBinary(*BinReader)
}