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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
vlen := val.Len()
|
slice := byteArrayBytes(val)
|
||||||
switch kind {
|
switch kind {
|
||||||
case Byte:
|
case Byte:
|
||||||
if vlen == 0 {
|
if len(slice) == 0 {
|
||||||
return &decodeError{msg: "input string too long", typ: val.Type()}
|
return &decodeError{msg: "input string too long", typ: val.Type()}
|
||||||
}
|
} else if len(slice) > 1 {
|
||||||
if vlen > 1 {
|
|
||||||
return &decodeError{msg: "input string too short", typ: val.Type()}
|
return &decodeError{msg: "input string too short", typ: val.Type()}
|
||||||
}
|
}
|
||||||
bv, _ := s.Uint()
|
slice[0] = s.byteval
|
||||||
val.Index(0).SetUint(bv)
|
s.kind = -1
|
||||||
case String:
|
case String:
|
||||||
if uint64(vlen) < size {
|
if uint64(len(slice)) < size {
|
||||||
return &decodeError{msg: "input string too long", typ: val.Type()}
|
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()}
|
return &decodeError{msg: "input string too short", typ: val.Type()}
|
||||||
}
|
}
|
||||||
slice := val.Slice(0, vlen).Interface().([]byte)
|
|
||||||
if err := s.readFull(slice); err != nil {
|
if err := s.readFull(slice); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,8 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStreamKind(t *testing.T) {
|
func TestStreamKind(t *testing.T) {
|
||||||
@ -1063,7 +1065,7 @@ func ExampleStream() {
|
|||||||
// [102 111 111 98 97 114] <nil>
|
// [102 111 111 98 97 114] <nil>
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkDecode(b *testing.B) {
|
func BenchmarkDecodeUints(b *testing.B) {
|
||||||
enc := encodeTestSlice(90000)
|
enc := encodeTestSlice(90000)
|
||||||
b.SetBytes(int64(len(enc)))
|
b.SetBytes(int64(len(enc)))
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
@ -1078,7 +1080,7 @@ func BenchmarkDecode(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkDecodeIntSliceReuse(b *testing.B) {
|
func BenchmarkDecodeUintsReused(b *testing.B) {
|
||||||
enc := encodeTestSlice(100000)
|
enc := encodeTestSlice(100000)
|
||||||
b.SetBytes(int64(len(enc)))
|
b.SetBytes(int64(len(enc)))
|
||||||
b.ReportAllocs()
|
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 {
|
func encodeTestSlice(n uint) []byte {
|
||||||
s := make([]uint, n)
|
s := make([]uint, n)
|
||||||
for i := uint(0); i < n; i++ {
|
for i := uint(0); i < n; i++ {
|
||||||
|
@ -124,19 +124,15 @@ func puthead(buf []byte, smalltag, largetag byte, size uint64) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type encbuf struct {
|
type encbuf struct {
|
||||||
str []byte // string data, contains everything except list headers
|
str []byte // string data, contains everything except list headers
|
||||||
lheads []listhead // all list headers
|
lheads []listhead // all list headers
|
||||||
lhsize int // sum of sizes of all encoded list headers
|
lhsize int // sum of sizes of all encoded list headers
|
||||||
sizebuf [9]byte // auxiliary buffer for uint encoding
|
sizebuf [9]byte // auxiliary buffer for uint encoding
|
||||||
bufvalue reflect.Value // used in writeByteArrayCopy
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// encbufs are pooled.
|
// encbufs are pooled.
|
||||||
var encbufPool = sync.Pool{
|
var encbufPool = sync.Pool{
|
||||||
New: func() interface{} {
|
New: func() interface{} { return new(encbuf) },
|
||||||
var bytes []byte
|
|
||||||
return &encbuf{bufvalue: reflect.ValueOf(&bytes).Elem()}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *encbuf) reset() {
|
func (w *encbuf) reset() {
|
||||||
@ -429,21 +425,14 @@ func writeBytes(val reflect.Value, w *encbuf) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var byteType = reflect.TypeOf(byte(0))
|
|
||||||
|
|
||||||
func makeByteArrayWriter(typ reflect.Type) writer {
|
func makeByteArrayWriter(typ reflect.Type) writer {
|
||||||
length := typ.Len()
|
switch typ.Len() {
|
||||||
if length == 0 {
|
case 0:
|
||||||
return writeLengthZeroByteArray
|
return writeLengthZeroByteArray
|
||||||
} else if length == 1 {
|
case 1:
|
||||||
return writeLengthOneByteArray
|
return writeLengthOneByteArray
|
||||||
}
|
default:
|
||||||
if typ.Elem() != byteType {
|
return writeByteArray
|
||||||
return writeNamedByteArray
|
|
||||||
}
|
|
||||||
return func(val reflect.Value, w *encbuf) error {
|
|
||||||
writeByteArrayCopy(length, val, w)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -462,29 +451,18 @@ func writeLengthOneByteArray(val reflect.Value, w *encbuf) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeByteArrayCopy encodes byte arrays using reflect.Copy. This is
|
func writeByteArray(val reflect.Value, w *encbuf) error {
|
||||||
// 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 {
|
|
||||||
if !val.CanAddr() {
|
if !val.CanAddr() {
|
||||||
// Slice requires the value to be addressable.
|
// Getting the byte slice of val requires it to be addressable. Make it
|
||||||
// Make it addressable by copying.
|
// addressable by copying.
|
||||||
copy := reflect.New(val.Type()).Elem()
|
copy := reflect.New(val.Type()).Elem()
|
||||||
copy.Set(val)
|
copy.Set(val)
|
||||||
val = copy
|
val = copy
|
||||||
}
|
}
|
||||||
size := val.Len()
|
|
||||||
slice := val.Slice(0, size).Bytes()
|
slice := byteArrayBytes(val)
|
||||||
w.encodeString(slice)
|
w.encodeStringHeader(len(slice))
|
||||||
|
w.str = append(w.str, slice...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -513,3 +513,22 @@ func BenchmarkEncodeConcurrentInterface(b *testing.B) {
|
|||||||
}
|
}
|
||||||
wg.Wait()
|
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