rlp: minor optimizations for slice/array encoding (#23467)
As per benchmark results below, these changes speed up encoding/decoding of consensus objects a bit. name old time/op new time/op delta EncodeRLP/legacy-header-8 384ns ± 1% 331ns ± 3% -13.83% (p=0.000 n=7+8) EncodeRLP/london-header-8 411ns ± 1% 359ns ± 2% -12.53% (p=0.000 n=8+8) EncodeRLP/receipt-for-storage-8 251ns ± 0% 239ns ± 0% -4.97% (p=0.000 n=8+8) EncodeRLP/receipt-full-8 319ns ± 0% 300ns ± 0% -5.89% (p=0.000 n=8+7) EncodeRLP/legacy-transaction-8 389ns ± 1% 387ns ± 1% ~ (p=0.099 n=8+8) EncodeRLP/access-transaction-8 607ns ± 0% 581ns ± 0% -4.26% (p=0.000 n=8+8) EncodeRLP/1559-transaction-8 627ns ± 0% 606ns ± 1% -3.44% (p=0.000 n=8+8) DecodeRLP/legacy-header-8 831ns ± 1% 813ns ± 1% -2.20% (p=0.000 n=8+8) DecodeRLP/london-header-8 824ns ± 0% 804ns ± 1% -2.44% (p=0.000 n=8+7) * rlp: pass length to byteArrayBytes This makes it possible to inline byteArrayBytes. For arrays, the length is known at encoder construction time, so the call to v.Len() can be avoided. * rlp: avoid IsNil for pointer encoding It's actually cheaper to use Elem first, because it performs less checks on the value. If the pointer was nil, the result of Elem is 'invalid'. * rlp: minor optimizations for slice/array encoding For empty slices/arrays, we can avoid storing a list header entry in the encoder buffer. Also avoid doing the tail check at encoding time because it is already known at encoder construction time.
This commit is contained in:
parent
8a134014b4
commit
32c576bd3c
|
@ -379,7 +379,7 @@ func decodeByteArray(s *Stream, val reflect.Value) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
slice := byteArrayBytes(val)
|
||||
slice := byteArrayBytes(val, val.Len())
|
||||
switch kind {
|
||||
case Byte:
|
||||
if len(slice) == 0 {
|
||||
|
|
107
rlp/encode.go
107
rlp/encode.go
|
@ -432,7 +432,20 @@ func makeByteArrayWriter(typ reflect.Type) writer {
|
|||
case 1:
|
||||
return writeLengthOneByteArray
|
||||
default:
|
||||
return writeByteArray
|
||||
length := typ.Len()
|
||||
return func(val reflect.Value, w *encbuf) error {
|
||||
if !val.CanAddr() {
|
||||
// Getting the byte slice of val requires it to be addressable. Make it
|
||||
// addressable by copying.
|
||||
copy := reflect.New(val.Type()).Elem()
|
||||
copy.Set(val)
|
||||
val = copy
|
||||
}
|
||||
slice := byteArrayBytes(val, length)
|
||||
w.encodeStringHeader(len(slice))
|
||||
w.str = append(w.str, slice...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -451,21 +464,6 @@ func writeLengthOneByteArray(val reflect.Value, w *encbuf) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func writeByteArray(val reflect.Value, w *encbuf) error {
|
||||
if !val.CanAddr() {
|
||||
// Getting the byte slice of val requires it to be addressable. Make it
|
||||
// addressable by copying.
|
||||
copy := reflect.New(val.Type()).Elem()
|
||||
copy.Set(val)
|
||||
val = copy
|
||||
}
|
||||
|
||||
slice := byteArrayBytes(val)
|
||||
w.encodeStringHeader(len(slice))
|
||||
w.str = append(w.str, slice...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeString(val reflect.Value, w *encbuf) error {
|
||||
s := val.String()
|
||||
if len(s) == 1 && s[0] <= 0x7f {
|
||||
|
@ -499,19 +497,39 @@ func makeSliceWriter(typ reflect.Type, ts tags) (writer, error) {
|
|||
if etypeinfo.writerErr != nil {
|
||||
return nil, etypeinfo.writerErr
|
||||
}
|
||||
writer := func(val reflect.Value, w *encbuf) error {
|
||||
if !ts.tail {
|
||||
defer w.listEnd(w.list())
|
||||
}
|
||||
vlen := val.Len()
|
||||
for i := 0; i < vlen; i++ {
|
||||
if err := etypeinfo.writer(val.Index(i), w); err != nil {
|
||||
return err
|
||||
|
||||
var wfn writer
|
||||
if ts.tail {
|
||||
// This is for struct tail slices.
|
||||
// w.list is not called for them.
|
||||
wfn = func(val reflect.Value, w *encbuf) error {
|
||||
vlen := val.Len()
|
||||
for i := 0; i < vlen; i++ {
|
||||
if err := etypeinfo.writer(val.Index(i), w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
// This is for regular slices and arrays.
|
||||
wfn = func(val reflect.Value, w *encbuf) error {
|
||||
vlen := val.Len()
|
||||
if vlen == 0 {
|
||||
w.str = append(w.str, 0xC0)
|
||||
return nil
|
||||
}
|
||||
listOffset := w.list()
|
||||
for i := 0; i < vlen; i++ {
|
||||
if err := etypeinfo.writer(val.Index(i), w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
w.listEnd(listOffset)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return writer, nil
|
||||
return wfn, nil
|
||||
}
|
||||
|
||||
func makeStructWriter(typ reflect.Type) (writer, error) {
|
||||
|
@ -562,12 +580,8 @@ func makeStructWriter(typ reflect.Type) (writer, error) {
|
|||
return writer, nil
|
||||
}
|
||||
|
||||
func makePtrWriter(typ reflect.Type, ts tags) (writer, error) {
|
||||
etypeinfo := theTC.infoWhileGenerating(typ.Elem(), tags{})
|
||||
if etypeinfo.writerErr != nil {
|
||||
return nil, etypeinfo.writerErr
|
||||
}
|
||||
// Determine how to encode nil pointers.
|
||||
// nilEncoding returns the encoded value of a nil pointer.
|
||||
func nilEncoding(typ reflect.Type, ts tags) uint8 {
|
||||
var nilKind Kind
|
||||
if ts.nilOK {
|
||||
nilKind = ts.nilKind // use struct tag if provided
|
||||
|
@ -575,16 +589,29 @@ func makePtrWriter(typ reflect.Type, ts tags) (writer, error) {
|
|||
nilKind = defaultNilKind(typ.Elem())
|
||||
}
|
||||
|
||||
switch nilKind {
|
||||
case String:
|
||||
return 0x80
|
||||
case List:
|
||||
return 0xC0
|
||||
default:
|
||||
panic(fmt.Errorf("rlp: invalid nil kind %d", nilKind))
|
||||
}
|
||||
}
|
||||
|
||||
func makePtrWriter(typ reflect.Type, ts tags) (writer, error) {
|
||||
etypeinfo := theTC.infoWhileGenerating(typ.Elem(), tags{})
|
||||
if etypeinfo.writerErr != nil {
|
||||
return nil, etypeinfo.writerErr
|
||||
}
|
||||
nilEncoding := nilEncoding(typ, ts)
|
||||
|
||||
writer := func(val reflect.Value, w *encbuf) error {
|
||||
if val.IsNil() {
|
||||
if nilKind == String {
|
||||
w.str = append(w.str, 0x80)
|
||||
} else {
|
||||
w.listEnd(w.list())
|
||||
}
|
||||
return nil
|
||||
if ev := val.Elem(); ev.IsValid() {
|
||||
return etypeinfo.writer(ev, w)
|
||||
}
|
||||
return etypeinfo.writer(val.Elem(), w)
|
||||
w.str = append(w.str, nilEncoding)
|
||||
return nil
|
||||
}
|
||||
return writer, nil
|
||||
}
|
||||
|
|
|
@ -540,3 +540,31 @@ func BenchmarkEncodeByteArrayStruct(b *testing.B) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
type structSliceElem struct {
|
||||
X uint64
|
||||
Y uint64
|
||||
Z uint64
|
||||
}
|
||||
|
||||
type structPtrSlice []*structSliceElem
|
||||
|
||||
func BenchmarkEncodeStructPtrSlice(b *testing.B) {
|
||||
var out bytes.Buffer
|
||||
var value = structPtrSlice{
|
||||
&structSliceElem{1, 1, 1},
|
||||
&structSliceElem{2, 2, 2},
|
||||
&structSliceElem{3, 3, 3},
|
||||
&structSliceElem{5, 5, 5},
|
||||
&structSliceElem{6, 6, 6},
|
||||
&structSliceElem{7, 7, 7},
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
out.Reset()
|
||||
if err := Encode(&out, &value); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,6 @@ package rlp
|
|||
import "reflect"
|
||||
|
||||
// byteArrayBytes returns a slice of the byte array v.
|
||||
func byteArrayBytes(v reflect.Value) []byte {
|
||||
return v.Slice(0, v.Len()).Bytes()
|
||||
func byteArrayBytes(v reflect.Value, length int) []byte {
|
||||
return v.Slice(0, length).Bytes()
|
||||
}
|
||||
|
|
|
@ -25,12 +25,11 @@ import (
|
|||
)
|
||||
|
||||
// byteArrayBytes returns a slice of the byte array v.
|
||||
func byteArrayBytes(v reflect.Value) []byte {
|
||||
len := v.Len()
|
||||
func byteArrayBytes(v reflect.Value, length int) []byte {
|
||||
var s []byte
|
||||
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&s))
|
||||
hdr.Data = v.UnsafeAddr()
|
||||
hdr.Cap = len
|
||||
hdr.Len = len
|
||||
hdr.Cap = length
|
||||
hdr.Len = length
|
||||
return s
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue