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:
Felix Lange 2021-08-25 19:01:10 +02:00 committed by GitHub
parent 8a134014b4
commit 32c576bd3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 101 additions and 47 deletions

@ -379,7 +379,7 @@ func decodeByteArray(s *Stream, val reflect.Value) error {
if err != nil { if err != nil {
return err return err
} }
slice := byteArrayBytes(val) slice := byteArrayBytes(val, val.Len())
switch kind { switch kind {
case Byte: case Byte:
if len(slice) == 0 { if len(slice) == 0 {

@ -432,7 +432,20 @@ func makeByteArrayWriter(typ reflect.Type) writer {
case 1: case 1:
return writeLengthOneByteArray return writeLengthOneByteArray
default: 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 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 { func writeString(val reflect.Value, w *encbuf) error {
s := val.String() s := val.String()
if len(s) == 1 && s[0] <= 0x7f { if len(s) == 1 && s[0] <= 0x7f {
@ -499,10 +497,12 @@ func makeSliceWriter(typ reflect.Type, ts tags) (writer, error) {
if etypeinfo.writerErr != nil { if etypeinfo.writerErr != nil {
return nil, etypeinfo.writerErr return nil, etypeinfo.writerErr
} }
writer := func(val reflect.Value, w *encbuf) error {
if !ts.tail { var wfn writer
defer w.listEnd(w.list()) 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() vlen := val.Len()
for i := 0; i < vlen; i++ { for i := 0; i < vlen; i++ {
if err := etypeinfo.writer(val.Index(i), w); err != nil { if err := etypeinfo.writer(val.Index(i), w); err != nil {
@ -511,7 +511,25 @@ func makeSliceWriter(typ reflect.Type, ts tags) (writer, error) {
} }
return nil return nil
} }
return writer, 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 wfn, nil
} }
func makeStructWriter(typ reflect.Type) (writer, error) { func makeStructWriter(typ reflect.Type) (writer, error) {
@ -562,12 +580,8 @@ func makeStructWriter(typ reflect.Type) (writer, error) {
return writer, nil return writer, nil
} }
func makePtrWriter(typ reflect.Type, ts tags) (writer, error) { // nilEncoding returns the encoded value of a nil pointer.
etypeinfo := theTC.infoWhileGenerating(typ.Elem(), tags{}) func nilEncoding(typ reflect.Type, ts tags) uint8 {
if etypeinfo.writerErr != nil {
return nil, etypeinfo.writerErr
}
// Determine how to encode nil pointers.
var nilKind Kind var nilKind Kind
if ts.nilOK { if ts.nilOK {
nilKind = ts.nilKind // use struct tag if provided nilKind = ts.nilKind // use struct tag if provided
@ -575,17 +589,30 @@ func makePtrWriter(typ reflect.Type, ts tags) (writer, error) {
nilKind = defaultNilKind(typ.Elem()) 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 { writer := func(val reflect.Value, w *encbuf) error {
if val.IsNil() { if ev := val.Elem(); ev.IsValid() {
if nilKind == String { return etypeinfo.writer(ev, w)
w.str = append(w.str, 0x80)
} else {
w.listEnd(w.list())
} }
w.str = append(w.str, nilEncoding)
return nil return nil
} }
return etypeinfo.writer(val.Elem(), w)
}
return writer, 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" import "reflect"
// byteArrayBytes returns a slice of the byte array v. // byteArrayBytes returns a slice of the byte array v.
func byteArrayBytes(v reflect.Value) []byte { func byteArrayBytes(v reflect.Value, length int) []byte {
return v.Slice(0, v.Len()).Bytes() return v.Slice(0, length).Bytes()
} }

@ -25,12 +25,11 @@ import (
) )
// byteArrayBytes returns a slice of the byte array v. // byteArrayBytes returns a slice of the byte array v.
func byteArrayBytes(v reflect.Value) []byte { func byteArrayBytes(v reflect.Value, length int) []byte {
len := v.Len()
var s []byte var s []byte
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&s)) hdr := (*reflect.SliceHeader)(unsafe.Pointer(&s))
hdr.Data = v.UnsafeAddr() hdr.Data = v.UnsafeAddr()
hdr.Cap = len hdr.Cap = length
hdr.Len = len hdr.Len = length
return s return s
} }