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:
		| @@ -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 | ||||
| } | ||||
		Reference in New Issue
	
	Block a user