Skip to content

Commit 32c576b

Browse files
authored
rlp: minor optimizations for slice/array encoding (ethereum#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.
1 parent 8a13401 commit 32c576b

File tree

5 files changed

+100
-46
lines changed

5 files changed

+100
-46
lines changed

rlp/decode.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ func decodeByteArray(s *Stream, val reflect.Value) error {
379379
if err != nil {
380380
return err
381381
}
382-
slice := byteArrayBytes(val)
382+
slice := byteArrayBytes(val, val.Len())
383383
switch kind {
384384
case Byte:
385385
if len(slice) == 0 {

rlp/encode.go

Lines changed: 66 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,20 @@ func makeByteArrayWriter(typ reflect.Type) writer {
432432
case 1:
433433
return writeLengthOneByteArray
434434
default:
435-
return writeByteArray
435+
length := typ.Len()
436+
return func(val reflect.Value, w *encbuf) error {
437+
if !val.CanAddr() {
438+
// Getting the byte slice of val requires it to be addressable. Make it
439+
// addressable by copying.
440+
copy := reflect.New(val.Type()).Elem()
441+
copy.Set(val)
442+
val = copy
443+
}
444+
slice := byteArrayBytes(val, length)
445+
w.encodeStringHeader(len(slice))
446+
w.str = append(w.str, slice...)
447+
return nil
448+
}
436449
}
437450
}
438451

@@ -451,21 +464,6 @@ func writeLengthOneByteArray(val reflect.Value, w *encbuf) error {
451464
return nil
452465
}
453466

454-
func writeByteArray(val reflect.Value, w *encbuf) error {
455-
if !val.CanAddr() {
456-
// Getting the byte slice of val requires it to be addressable. Make it
457-
// addressable by copying.
458-
copy := reflect.New(val.Type()).Elem()
459-
copy.Set(val)
460-
val = copy
461-
}
462-
463-
slice := byteArrayBytes(val)
464-
w.encodeStringHeader(len(slice))
465-
w.str = append(w.str, slice...)
466-
return nil
467-
}
468-
469467
func writeString(val reflect.Value, w *encbuf) error {
470468
s := val.String()
471469
if len(s) == 1 && s[0] <= 0x7f {
@@ -499,19 +497,39 @@ func makeSliceWriter(typ reflect.Type, ts tags) (writer, error) {
499497
if etypeinfo.writerErr != nil {
500498
return nil, etypeinfo.writerErr
501499
}
502-
writer := func(val reflect.Value, w *encbuf) error {
503-
if !ts.tail {
504-
defer w.listEnd(w.list())
500+
501+
var wfn writer
502+
if ts.tail {
503+
// This is for struct tail slices.
504+
// w.list is not called for them.
505+
wfn = func(val reflect.Value, w *encbuf) error {
506+
vlen := val.Len()
507+
for i := 0; i < vlen; i++ {
508+
if err := etypeinfo.writer(val.Index(i), w); err != nil {
509+
return err
510+
}
511+
}
512+
return nil
505513
}
506-
vlen := val.Len()
507-
for i := 0; i < vlen; i++ {
508-
if err := etypeinfo.writer(val.Index(i), w); err != nil {
509-
return err
514+
} else {
515+
// This is for regular slices and arrays.
516+
wfn = func(val reflect.Value, w *encbuf) error {
517+
vlen := val.Len()
518+
if vlen == 0 {
519+
w.str = append(w.str, 0xC0)
520+
return nil
510521
}
522+
listOffset := w.list()
523+
for i := 0; i < vlen; i++ {
524+
if err := etypeinfo.writer(val.Index(i), w); err != nil {
525+
return err
526+
}
527+
}
528+
w.listEnd(listOffset)
529+
return nil
511530
}
512-
return nil
513531
}
514-
return writer, nil
532+
return wfn, nil
515533
}
516534

517535
func makeStructWriter(typ reflect.Type) (writer, error) {
@@ -562,29 +580,38 @@ func makeStructWriter(typ reflect.Type) (writer, error) {
562580
return writer, nil
563581
}
564582

565-
func makePtrWriter(typ reflect.Type, ts tags) (writer, error) {
566-
etypeinfo := theTC.infoWhileGenerating(typ.Elem(), tags{})
567-
if etypeinfo.writerErr != nil {
568-
return nil, etypeinfo.writerErr
569-
}
570-
// Determine how to encode nil pointers.
583+
// nilEncoding returns the encoded value of a nil pointer.
584+
func nilEncoding(typ reflect.Type, ts tags) uint8 {
571585
var nilKind Kind
572586
if ts.nilOK {
573587
nilKind = ts.nilKind // use struct tag if provided
574588
} else {
575589
nilKind = defaultNilKind(typ.Elem())
576590
}
577591

592+
switch nilKind {
593+
case String:
594+
return 0x80
595+
case List:
596+
return 0xC0
597+
default:
598+
panic(fmt.Errorf("rlp: invalid nil kind %d", nilKind))
599+
}
600+
}
601+
602+
func makePtrWriter(typ reflect.Type, ts tags) (writer, error) {
603+
etypeinfo := theTC.infoWhileGenerating(typ.Elem(), tags{})
604+
if etypeinfo.writerErr != nil {
605+
return nil, etypeinfo.writerErr
606+
}
607+
nilEncoding := nilEncoding(typ, ts)
608+
578609
writer := func(val reflect.Value, w *encbuf) error {
579-
if val.IsNil() {
580-
if nilKind == String {
581-
w.str = append(w.str, 0x80)
582-
} else {
583-
w.listEnd(w.list())
584-
}
585-
return nil
610+
if ev := val.Elem(); ev.IsValid() {
611+
return etypeinfo.writer(ev, w)
586612
}
587-
return etypeinfo.writer(val.Elem(), w)
613+
w.str = append(w.str, nilEncoding)
614+
return nil
588615
}
589616
return writer, nil
590617
}

rlp/encode_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,3 +540,31 @@ func BenchmarkEncodeByteArrayStruct(b *testing.B) {
540540
}
541541
}
542542
}
543+
544+
type structSliceElem struct {
545+
X uint64
546+
Y uint64
547+
Z uint64
548+
}
549+
550+
type structPtrSlice []*structSliceElem
551+
552+
func BenchmarkEncodeStructPtrSlice(b *testing.B) {
553+
var out bytes.Buffer
554+
var value = structPtrSlice{
555+
&structSliceElem{1, 1, 1},
556+
&structSliceElem{2, 2, 2},
557+
&structSliceElem{3, 3, 3},
558+
&structSliceElem{5, 5, 5},
559+
&structSliceElem{6, 6, 6},
560+
&structSliceElem{7, 7, 7},
561+
}
562+
563+
b.ReportAllocs()
564+
for i := 0; i < b.N; i++ {
565+
out.Reset()
566+
if err := Encode(&out, &value); err != nil {
567+
b.Fatal(err)
568+
}
569+
}
570+
}

rlp/safe.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@ package rlp
2222
import "reflect"
2323

2424
// byteArrayBytes returns a slice of the byte array v.
25-
func byteArrayBytes(v reflect.Value) []byte {
26-
return v.Slice(0, v.Len()).Bytes()
25+
func byteArrayBytes(v reflect.Value, length int) []byte {
26+
return v.Slice(0, length).Bytes()
2727
}

rlp/unsafe.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,11 @@ import (
2525
)
2626

2727
// byteArrayBytes returns a slice of the byte array v.
28-
func byteArrayBytes(v reflect.Value) []byte {
29-
len := v.Len()
28+
func byteArrayBytes(v reflect.Value, length int) []byte {
3029
var s []byte
3130
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&s))
3231
hdr.Data = v.UnsafeAddr()
33-
hdr.Cap = len
34-
hdr.Len = len
32+
hdr.Cap = length
33+
hdr.Len = length
3534
return s
3635
}

0 commit comments

Comments
 (0)