Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rlp: minor optimizations for slice/array encoding #23467

Merged
merged 4 commits into from Aug 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion rlp/decode.go
Expand Up @@ -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 {
Expand Down
105 changes: 66 additions & 39 deletions rlp/encode.go
Expand Up @@ -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
}
}
}

Expand All @@ -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 {
Expand Down Expand Up @@ -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())

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
}
vlen := val.Len()
for i := 0; i < vlen; i++ {
if err := etypeinfo.writer(val.Index(i), w); err != nil {
return err
} 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) {
Expand Down Expand Up @@ -562,29 +580,38 @@ 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
} else {
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
}
Expand Down
28 changes: 28 additions & 0 deletions rlp/encode_test.go
Expand Up @@ -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)
}
}
}
4 changes: 2 additions & 2 deletions rlp/safe.go
Expand Up @@ -21,6 +21,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()
}
7 changes: 3 additions & 4 deletions rlp/unsafe.go
Expand Up @@ -24,12 +24,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
}