rlp: optimize byte array handling (#22924)
This change improves the performance of encoding/decoding [N]byte. name old time/op new time/op delta DecodeByteArrayStruct-8 336ns ± 0% 246ns ± 0% -26.98% (p=0.000 n=9+10) EncodeByteArrayStruct-8 225ns ± 1% 148ns ± 1% -34.12% (p=0.000 n=10+10) name old alloc/op new alloc/op delta DecodeByteArrayStruct-8 120B ± 0% 48B ± 0% -60.00% (p=0.000 n=10+10) EncodeByteArrayStruct-8 0.00B 0.00B ~ (all equal)
This commit is contained in:
parent
0d076d92db
commit
154ca32a8a
@ -348,25 +348,23 @@ func decodeByteArray(s *Stream, val reflect.Value) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vlen := val.Len()
|
||||
slice := byteArrayBytes(val)
|
||||
switch kind {
|
||||
case Byte:
|
||||
if vlen == 0 {
|
||||
if len(slice) == 0 {
|
||||
return &decodeError{msg: "input string too long", typ: val.Type()}
|
||||
}
|
||||
if vlen > 1 {
|
||||
} else if len(slice) > 1 {
|
||||
return &decodeError{msg: "input string too short", typ: val.Type()}
|
||||
}
|
||||
bv, _ := s.Uint()
|
||||
val.Index(0).SetUint(bv)
|
||||
slice[0] = s.byteval
|
||||
s.kind = -1
|
||||
case String:
|
||||
if uint64(vlen) < size {
|
||||
if uint64(len(slice)) < size {
|
||||
return &decodeError{msg: "input string too long", typ: val.Type()}
|
||||
}
|
||||
if uint64(vlen) > size {
|
||||
if uint64(len(slice)) > size {
|
||||
return &decodeError{msg: "input string too short", typ: val.Type()}
|
||||
}
|
||||
slice := val.Slice(0, vlen).Interface().([]byte)
|
||||
if err := s.readFull(slice); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -26,6 +26,8 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
)
|
||||
|
||||
func TestStreamKind(t *testing.T) {
|
||||
@ -1063,7 +1065,7 @@ func ExampleStream() {
|
||||
// [102 111 111 98 97 114] <nil>
|
||||
}
|
||||
|
||||
func BenchmarkDecode(b *testing.B) {
|
||||
func BenchmarkDecodeUints(b *testing.B) {
|
||||
enc := encodeTestSlice(90000)
|
||||
b.SetBytes(int64(len(enc)))
|
||||
b.ReportAllocs()
|
||||
@ -1078,7 +1080,7 @@ func BenchmarkDecode(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecodeIntSliceReuse(b *testing.B) {
|
||||
func BenchmarkDecodeUintsReused(b *testing.B) {
|
||||
enc := encodeTestSlice(100000)
|
||||
b.SetBytes(int64(len(enc)))
|
||||
b.ReportAllocs()
|
||||
@ -1093,6 +1095,44 @@ func BenchmarkDecodeIntSliceReuse(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecodeByteArrayStruct(b *testing.B) {
|
||||
enc, err := EncodeToBytes(&byteArrayStruct{})
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.SetBytes(int64(len(enc)))
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
var out byteArrayStruct
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := DecodeBytes(enc, &out); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecodeBigInts(b *testing.B) {
|
||||
ints := make([]*big.Int, 200)
|
||||
for i := range ints {
|
||||
ints[i] = math.BigPow(2, int64(i))
|
||||
}
|
||||
enc, err := EncodeToBytes(ints)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.SetBytes(int64(len(enc)))
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
var out []*big.Int
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := DecodeBytes(enc, &out); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func encodeTestSlice(n uint) []byte {
|
||||
s := make([]uint, n)
|
||||
for i := uint(0); i < n; i++ {
|
||||
|
@ -124,19 +124,15 @@ func puthead(buf []byte, smalltag, largetag byte, size uint64) int {
|
||||
}
|
||||
|
||||
type encbuf struct {
|
||||
str []byte // string data, contains everything except list headers
|
||||
lheads []listhead // all list headers
|
||||
lhsize int // sum of sizes of all encoded list headers
|
||||
sizebuf [9]byte // auxiliary buffer for uint encoding
|
||||
bufvalue reflect.Value // used in writeByteArrayCopy
|
||||
str []byte // string data, contains everything except list headers
|
||||
lheads []listhead // all list headers
|
||||
lhsize int // sum of sizes of all encoded list headers
|
||||
sizebuf [9]byte // auxiliary buffer for uint encoding
|
||||
}
|
||||
|
||||
// encbufs are pooled.
|
||||
var encbufPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
var bytes []byte
|
||||
return &encbuf{bufvalue: reflect.ValueOf(&bytes).Elem()}
|
||||
},
|
||||
New: func() interface{} { return new(encbuf) },
|
||||
}
|
||||
|
||||
func (w *encbuf) reset() {
|
||||
@ -429,21 +425,14 @@ func writeBytes(val reflect.Value, w *encbuf) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var byteType = reflect.TypeOf(byte(0))
|
||||
|
||||
func makeByteArrayWriter(typ reflect.Type) writer {
|
||||
length := typ.Len()
|
||||
if length == 0 {
|
||||
switch typ.Len() {
|
||||
case 0:
|
||||
return writeLengthZeroByteArray
|
||||
} else if length == 1 {
|
||||
case 1:
|
||||
return writeLengthOneByteArray
|
||||
}
|
||||
if typ.Elem() != byteType {
|
||||
return writeNamedByteArray
|
||||
}
|
||||
return func(val reflect.Value, w *encbuf) error {
|
||||
writeByteArrayCopy(length, val, w)
|
||||
return nil
|
||||
default:
|
||||
return writeByteArray
|
||||
}
|
||||
}
|
||||
|
||||
@ -462,29 +451,18 @@ func writeLengthOneByteArray(val reflect.Value, w *encbuf) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeByteArrayCopy encodes byte arrays using reflect.Copy. This is
|
||||
// the fast path for [N]byte where N > 1.
|
||||
func writeByteArrayCopy(length int, val reflect.Value, w *encbuf) {
|
||||
w.encodeStringHeader(length)
|
||||
offset := len(w.str)
|
||||
w.str = append(w.str, make([]byte, length)...)
|
||||
w.bufvalue.SetBytes(w.str[offset:])
|
||||
reflect.Copy(w.bufvalue, val)
|
||||
}
|
||||
|
||||
// writeNamedByteArray encodes byte arrays with named element type.
|
||||
// This exists because reflect.Copy can't be used with such types.
|
||||
func writeNamedByteArray(val reflect.Value, w *encbuf) error {
|
||||
func writeByteArray(val reflect.Value, w *encbuf) error {
|
||||
if !val.CanAddr() {
|
||||
// Slice requires the value to be addressable.
|
||||
// Make it addressable by copying.
|
||||
// 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
|
||||
}
|
||||
size := val.Len()
|
||||
slice := val.Slice(0, size).Bytes()
|
||||
w.encodeString(slice)
|
||||
|
||||
slice := byteArrayBytes(val)
|
||||
w.encodeStringHeader(len(slice))
|
||||
w.str = append(w.str, slice...)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -513,3 +513,22 @@ func BenchmarkEncodeConcurrentInterface(b *testing.B) {
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
type byteArrayStruct struct {
|
||||
A [20]byte
|
||||
B [32]byte
|
||||
C [32]byte
|
||||
}
|
||||
|
||||
func BenchmarkEncodeByteArrayStruct(b *testing.B) {
|
||||
var out bytes.Buffer
|
||||
var value byteArrayStruct
|
||||
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
out.Reset()
|
||||
if err := Encode(&out, &value); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
26
rlp/safe.go
Normal file
26
rlp/safe.go
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// +build nacl js !cgo
|
||||
|
||||
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()
|
||||
}
|
35
rlp/unsafe.go
Normal file
35
rlp/unsafe.go
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// +build !nacl,!js,cgo
|
||||
|
||||
package rlp
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// byteArrayBytes returns a slice of the byte array v.
|
||||
func byteArrayBytes(v reflect.Value) []byte {
|
||||
len := v.Len()
|
||||
var s []byte
|
||||
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&s))
|
||||
hdr.Data = v.UnsafeAddr()
|
||||
hdr.Cap = len
|
||||
hdr.Len = len
|
||||
return s
|
||||
}
|
Loading…
Reference in New Issue
Block a user