Skip to content

Commit

Permalink
Faster trie node encode (#533)
Browse files Browse the repository at this point in the history
* lowrlp: remove inner pool caching Encoder objects

* trie: speedup trie node encoding

* cache encoder in hasher object
* add encode/encodeTrailing method for node interface
  • Loading branch information
qianbin authored Jun 20, 2022
1 parent bccd082 commit 97635fe
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 103 deletions.
16 changes: 0 additions & 16 deletions lowrlp/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ package lowrlp

import (
"io"
"sync"
)

// Encoder is the low-level rlp encoder.
Expand All @@ -20,28 +19,13 @@ type Encoder struct {
sizebuf [9]byte // auxiliary buffer for uint encoding
}

var pool = sync.Pool{
New: func() interface{} { return &Encoder{} },
}

func NewEncoder() *Encoder {
w := pool.Get().(*Encoder)
w.Reset()
return w
}

// Reset reset the encoder state.
func (w *Encoder) Reset() {
w.lhsize = 0
w.str = w.str[:0]
w.lheads = w.lheads[:0]
}

// Release puts back the inner state into pool.
func (w *Encoder) Release() {
pool.Put(w)
}

// EncodeString encodes the string value.
func (w *Encoder) EncodeString(b []byte) {
if len(b) == 1 && b[0] <= 0x7F {
Expand Down
3 changes: 1 addition & 2 deletions state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -462,13 +462,12 @@ func (s *State) Stage(newBlockNum, newBlockConflicts uint32) (*Stage, error) {
c.storage[key.key] = v.(rlp.RawValue)
if len(c.meta.StorageID) == 0 {
// generate storage id for the new storage trie.
enc := lowrlp.NewEncoder()
var enc lowrlp.Encoder
enc.EncodeUint(uint64(newBlockNum))
enc.EncodeUint(uint64(newBlockConflicts))
enc.EncodeUint(uint64(storageTrieCreationCount))
storageTrieCreationCount++
c.meta.StorageID = enc.ToBytes()
enc.Release()
}
case storageBarrierKey:
if c, jerr = getChanged(thor.Address(key)); jerr != nil {
Expand Down
113 changes: 46 additions & 67 deletions trie/fast_node_encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,87 +6,66 @@
package trie

import (
"io"

"github.com/vechain/thor/lowrlp"
)

// fastNodeEncoder is the fast node encoder using low-level rlp encoder.
type fastNodeEncoder struct{}
// implements node.encode and node.encodeTrailing

var frlp fastNodeEncoder
func (n *fullNode) encode(e *lowrlp.Encoder, nonCrypto bool) {
off := e.List()
for _, c := range n.Children {
if c != nil {
c.encode(e, nonCrypto)
} else {
e.EncodeEmptyString()
}
}
e.ListEnd(off)
}

// Encode writes the RLP encoding of node to w.
func (fastNodeEncoder) Encode(w io.Writer, node node, nonCrypto bool) error {
enc := lowrlp.NewEncoder()
defer enc.Release()
func (n *fullNode) encodeTrailing(e *lowrlp.Encoder) {
for _, c := range n.Children {
if c != nil {
c.encodeTrailing(e)
}
}
}

fastEncodeNode(enc, node, nonCrypto)
return enc.ToWriter(w)
func (n *shortNode) encode(e *lowrlp.Encoder, nonCrypto bool) {
off := e.List()
e.EncodeString(n.Key)
if n.Val != nil {
n.Val.encode(e, nonCrypto)
} else {
e.EncodeEmptyString()
}
e.ListEnd(off)
}

// EncodeToBytes returns the RLP encoding of node.
func (fastNodeEncoder) EncodeToBytes(collapsed node, nonCrypto bool) []byte {
enc := lowrlp.NewEncoder()
defer enc.Release()
func (n *shortNode) encodeTrailing(e *lowrlp.Encoder) {
if n.Val != nil {
n.Val.encodeTrailing(e)
}
}

fastEncodeNode(enc, collapsed, nonCrypto)
return enc.ToBytes()
func (n *hashNode) encode(e *lowrlp.Encoder, nonCrypto bool) {
if nonCrypto {
e.EncodeString(nonCryptoNodeHashPlaceholder)
} else {
e.EncodeString(n.Hash[:])
}
}

func (fastNodeEncoder) EncodeTrailing(w io.Writer, collapsed node) error {
enc := lowrlp.NewEncoder()
defer enc.Release()
fastEncodeNodeTrailing(enc, collapsed)
return enc.ToWriter(w)
func (n *hashNode) encodeTrailing(e *lowrlp.Encoder) {
e.EncodeUint(n.seq)
}

func fastEncodeNode(w *lowrlp.Encoder, collapsed node, nonCrypto bool) {
switch n := collapsed.(type) {
case *fullNode:
offset := w.List()
for _, c := range n.Children {
if c != nil {
fastEncodeNode(w, c, nonCrypto)
} else {
w.EncodeEmptyString()
}
}
w.ListEnd(offset)
case *shortNode:
offset := w.List()
w.EncodeString(n.Key)
if n.Val != nil {
fastEncodeNode(w, n.Val, nonCrypto)
} else {
w.EncodeEmptyString()
}
w.ListEnd(offset)
case *hashNode:
if nonCrypto {
w.EncodeString(nonCryptoNodeHashPlaceholder)
} else {
w.EncodeString(n.Hash[:])
}
case *valueNode:
w.EncodeString(n.Value)
}
func (n *valueNode) encode(e *lowrlp.Encoder, nonCrypto bool) {
e.EncodeString(n.Value)
}

func fastEncodeNodeTrailing(w *lowrlp.Encoder, collapsed node) {
switch n := collapsed.(type) {
case *shortNode:
fastEncodeNodeTrailing(w, n.Val)
case *fullNode:
for _, c := range n.Children {
fastEncodeNodeTrailing(w, c)
}
case *valueNode:
// skip empty node
if len(n.Value) > 0 {
w.EncodeString(n.meta)
}
case *hashNode:
w.EncodeUint(n.seq)
func (n *valueNode) encodeTrailing(e *lowrlp.Encoder) {
if len(n.Value) > 0 {
e.EncodeString(n.meta)
}
}
14 changes: 8 additions & 6 deletions trie/hasher.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ import (
"sync"

"github.com/ethereum/go-ethereum/common"
"github.com/vechain/thor/lowrlp"
"github.com/vechain/thor/thor"
)

type hasher struct {
enc lowrlp.Encoder
tmp sliceBuffer
cacheGen uint16
cacheTTL uint16
Expand Down Expand Up @@ -188,10 +190,10 @@ func (h *hasher) store(n node, db DatabaseWriter, path []byte, force bool) (node
return n, nil
}
// Generate the RLP encoding of the node
h.enc.Reset()
n.encode(&h.enc, h.nonCrypto)
h.tmp.Reset()
if err := frlp.Encode(&h.tmp, n, h.nonCrypto); err != nil {
panic("encode error: " + err.Error())
}
h.enc.ToWriter(&h.tmp)

if h.nonCrypto {
// fullnode and shortnode with non-value child are forced
Expand Down Expand Up @@ -225,9 +227,9 @@ func (h *hasher) store(n node, db DatabaseWriter, path []byte, force bool) (node
if db != nil {
// extended
if h.extended {
if err := frlp.EncodeTrailing(&h.tmp, n); err != nil {
return nil, err
}
h.enc.Reset()
n.encodeTrailing(&h.enc)
h.enc.ToWriter(&h.tmp)
hash.seq = h.seq
}

Expand Down
14 changes: 8 additions & 6 deletions trie/iterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,12 +183,13 @@ func (it *nodeIterator) Node(handler func(blob []byte) error) error {

collapsed, _, _ := h.hashChildren(st.node, nil, it.path)

h.tmp.Reset()
_ = frlp.Encode(&h.tmp, collapsed, it.nonCrypto)

h.enc.Reset()
collapsed.encode(&h.enc, h.nonCrypto)
if it.extended {
_ = frlp.EncodeTrailing(&h.tmp, collapsed)
collapsed.encodeTrailing(&h.enc)
}
h.tmp.Reset()
h.enc.ToWriter(&h.tmp)
return handler(h.tmp)
}

Expand Down Expand Up @@ -239,8 +240,9 @@ func (it *nodeIterator) LeafProof() [][]byte {
node, _, _ := hasher.hashChildren(item.node, nil, nil)
hashed, _ := hasher.store(node, nil, nil, false)
if _, ok := hashed.(*hashNode); ok || i == 0 {
enc := frlp.EncodeToBytes(node, false)
proofs = append(proofs, enc)
hasher.enc.Reset()
node.encode(&hasher.enc, hasher.nonCrypto)
proofs = append(proofs, hasher.enc.ToBytes())
}
}
return proofs
Expand Down
3 changes: 3 additions & 0 deletions trie/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"strings"

"github.com/ethereum/go-ethereum/rlp"
"github.com/vechain/thor/lowrlp"
"github.com/vechain/thor/thor"
)

Expand All @@ -36,6 +37,8 @@ type node interface {
fstring(string) string
cache() (*hashNode, bool, uint16)
seqNum() uint64
encode(e *lowrlp.Encoder, nonCrypto bool)
encodeTrailing(*lowrlp.Encoder)
}

type (
Expand Down
9 changes: 6 additions & 3 deletions trie/node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,17 @@ func BenchmarkEncodeFullNode(b *testing.B) {
}

func BenchmarkFastEncodeFullNode(b *testing.B) {
var buf sliceBuffer
f := &fullNode{}
for i := 0; i < len(f.Children); i++ {
f.Children[i] = &hashNode{Hash: thor.BytesToBytes32(randBytes(32))}
}

h := newHasher(0, 0)

for i := 0; i < b.N; i++ {
buf.Reset()
frlp.Encode(&buf, f, false)
h.enc.Reset()
f.encode(&h.enc, false)
h.tmp.Reset()
h.enc.ToWriter(&h.tmp)
}
}
9 changes: 6 additions & 3 deletions trie/proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,14 @@ func (t *Trie) Prove(key []byte, fromLevel uint, proofDb DatabaseWriter) error {
if fromLevel > 0 {
fromLevel--
} else {
enc := frlp.EncodeToBytes(n, false)
hasher.enc.Reset()
n.encode(&hasher.enc, hasher.nonCrypto)
hasher.tmp.Reset()
hasher.enc.ToWriter(&hasher.tmp)
if ok {
proofDb.Put(hash.Hash[:], enc)
proofDb.Put(hash.Hash[:], hasher.tmp)
} else {
proofDb.Put(thor.Blake2b(enc).Bytes(), enc)
proofDb.Put(thor.Blake2b(hasher.tmp).Bytes(), hasher.tmp)
}
}
}
Expand Down

0 comments on commit 97635fe

Please sign in to comment.