rlp, trie: faster trie node encoding (#24126)
This change speeds up trie hashing and all other activities that require RLP encoding of trie nodes by approximately 20%. The speedup is achieved by avoiding reflection overhead during node encoding. The interface type trie.node now contains a method 'encode' that works with rlp.EncoderBuffer. Management of EncoderBuffers is left to calling code. trie.hasher, which is pooled to avoid allocations, now maintains an EncoderBuffer. This means memory resources related to trie node encoding are tied to the hasher pool. Co-authored-by: Felix Lange <fjl@twurst.com>
This commit is contained in:
@ -36,27 +36,31 @@ func (buf *encBuffer) size() int {
|
||||
return len(buf.str) + buf.lhsize
|
||||
}
|
||||
|
||||
// toBytes creates the encoder output.
|
||||
func (w *encBuffer) toBytes() []byte {
|
||||
// makeBytes creates the encoder output.
|
||||
func (w *encBuffer) makeBytes() []byte {
|
||||
out := make([]byte, w.size())
|
||||
w.copyTo(out)
|
||||
return out
|
||||
}
|
||||
|
||||
func (w *encBuffer) copyTo(dst []byte) {
|
||||
strpos := 0
|
||||
pos := 0
|
||||
for _, head := range w.lheads {
|
||||
// write string data before header
|
||||
n := copy(out[pos:], w.str[strpos:head.offset])
|
||||
n := copy(dst[pos:], w.str[strpos:head.offset])
|
||||
pos += n
|
||||
strpos += n
|
||||
// write the header
|
||||
enc := head.encode(out[pos:])
|
||||
enc := head.encode(dst[pos:])
|
||||
pos += len(enc)
|
||||
}
|
||||
// copy string data after the last list header
|
||||
copy(out[pos:], w.str[strpos:])
|
||||
return out
|
||||
copy(dst[pos:], w.str[strpos:])
|
||||
}
|
||||
|
||||
// toWriter writes the encoder output to w.
|
||||
func (buf *encBuffer) toWriter(w io.Writer) (err error) {
|
||||
// writeTo writes the encoder output to w.
|
||||
func (buf *encBuffer) writeTo(w io.Writer) (err error) {
|
||||
strpos := 0
|
||||
for _, head := range buf.lheads {
|
||||
// write string data before header
|
||||
@ -252,6 +256,19 @@ func (r *encReader) next() []byte {
|
||||
}
|
||||
}
|
||||
|
||||
func encBufferFromWriter(w io.Writer) *encBuffer {
|
||||
switch w := w.(type) {
|
||||
case EncoderBuffer:
|
||||
return w.buf
|
||||
case *EncoderBuffer:
|
||||
return w.buf
|
||||
case *encBuffer:
|
||||
return w
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// EncoderBuffer is a buffer for incremental encoding.
|
||||
//
|
||||
// The zero value is NOT ready for use. To get a usable buffer,
|
||||
@ -279,14 +296,10 @@ func (w *EncoderBuffer) Reset(dst io.Writer) {
|
||||
// If the destination writer has an *encBuffer, use it.
|
||||
// Note that w.ownBuffer is left false here.
|
||||
if dst != nil {
|
||||
if outer, ok := dst.(*encBuffer); ok {
|
||||
if outer := encBufferFromWriter(dst); outer != nil {
|
||||
*w = EncoderBuffer{outer, nil, false}
|
||||
return
|
||||
}
|
||||
if outer, ok := dst.(EncoderBuffer); ok {
|
||||
*w = EncoderBuffer{outer.buf, nil, false}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Get a fresh buffer.
|
||||
@ -303,7 +316,7 @@ func (w *EncoderBuffer) Reset(dst io.Writer) {
|
||||
func (w *EncoderBuffer) Flush() error {
|
||||
var err error
|
||||
if w.dst != nil {
|
||||
err = w.buf.toWriter(w.dst)
|
||||
err = w.buf.writeTo(w.dst)
|
||||
}
|
||||
// Release the internal buffer.
|
||||
if w.ownBuffer {
|
||||
@ -315,7 +328,15 @@ func (w *EncoderBuffer) Flush() error {
|
||||
|
||||
// ToBytes returns the encoded bytes.
|
||||
func (w *EncoderBuffer) ToBytes() []byte {
|
||||
return w.buf.toBytes()
|
||||
return w.buf.makeBytes()
|
||||
}
|
||||
|
||||
// AppendToBytes appends the encoded bytes to dst.
|
||||
func (w *EncoderBuffer) AppendToBytes(dst []byte) []byte {
|
||||
size := w.buf.size()
|
||||
out := append(dst, make([]byte, size)...)
|
||||
w.buf.copyTo(out[len(dst):])
|
||||
return out
|
||||
}
|
||||
|
||||
// Write appends b directly to the encoder output.
|
||||
|
@ -56,20 +56,16 @@ type Encoder interface {
|
||||
// Please see package-level documentation of encoding rules.
|
||||
func Encode(w io.Writer, val interface{}) error {
|
||||
// Optimization: reuse *encBuffer when called by EncodeRLP.
|
||||
if buf, ok := w.(*encBuffer); ok {
|
||||
if buf := encBufferFromWriter(w); buf != nil {
|
||||
return buf.encode(val)
|
||||
}
|
||||
if ebuf, ok := w.(EncoderBuffer); ok {
|
||||
return ebuf.buf.encode(val)
|
||||
}
|
||||
|
||||
buf := getEncBuffer()
|
||||
defer encBufferPool.Put(buf)
|
||||
|
||||
if err := buf.encode(val); err != nil {
|
||||
return err
|
||||
}
|
||||
return buf.toWriter(w)
|
||||
return buf.writeTo(w)
|
||||
}
|
||||
|
||||
// EncodeToBytes returns the RLP encoding of val.
|
||||
@ -81,7 +77,7 @@ func EncodeToBytes(val interface{}) ([]byte, error) {
|
||||
if err := buf.encode(val); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.toBytes(), nil
|
||||
return buf.makeBytes(), nil
|
||||
}
|
||||
|
||||
// EncodeToReader returns a reader from which the RLP encoding of val
|
||||
|
@ -399,6 +399,21 @@ func TestEncodeToBytes(t *testing.T) {
|
||||
runEncTests(t, EncodeToBytes)
|
||||
}
|
||||
|
||||
func TestEncodeAppendToBytes(t *testing.T) {
|
||||
buffer := make([]byte, 20)
|
||||
runEncTests(t, func(val interface{}) ([]byte, error) {
|
||||
w := NewEncoderBuffer(nil)
|
||||
defer w.Flush()
|
||||
|
||||
err := Encode(w, val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
output := w.AppendToBytes(buffer[:0])
|
||||
return output, nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestEncodeToReader(t *testing.T) {
|
||||
runEncTests(t, func(val interface{}) ([]byte, error) {
|
||||
_, r, err := EncodeToReader(val)
|
||||
|
Reference in New Issue
Block a user