trie: polish the code

This commit is contained in:
Gary Rong 2025-01-21 15:31:02 +08:00
parent bfc3aaee4c
commit 140be4e19e
5 changed files with 63 additions and 39 deletions

View file

@ -42,6 +42,15 @@ func (bp *bytesPool) Get() []byte {
} }
} }
// GetWithSize returns a slice with specified byte slice size.
func (bp *bytesPool) GetWithSize(s int) []byte {
b := bp.Get()
if cap(b) < s {
return make([]byte, s)
}
return b[:s]
}
// Put returns a slice to the pool. Safe for concurrent use. This method // Put returns a slice to the pool. Safe for concurrent use. This method
// will ignore slices that are too small or too large (>3x the cap) // will ignore slices that are too small or too large (>3x the cap)
func (bp *bytesPool) Put(b []byte) { func (bp *bytesPool) Put(b []byte) {

View file

@ -107,12 +107,13 @@ func keybytesToHex(str []byte) []byte {
// writeHexKey writes the hexkey into the given slice. // writeHexKey writes the hexkey into the given slice.
// OBS! This method omits the termination flag. // OBS! This method omits the termination flag.
// OBS! The dst slice must be at least 2x as large as the key // OBS! The dst slice must be at least 2x as large as the key
func writeHexKey(dst []byte, key []byte) { func writeHexKey(dst []byte, key []byte) []byte {
_ = dst[2*len(key)-1] _ = dst[2*len(key)-1]
for i, b := range key { for i, b := range key {
dst[i*2] = b / 16 dst[i*2] = b / 16
dst[i*2+1] = b % 16 dst[i*2+1] = b % 16
} }
return dst[:2*len(key)]
} }
// hexToKeybytes turns hex nibbles into key bytes. // hexToKeybytes turns hex nibbles into key bytes.

View file

@ -46,17 +46,23 @@ type (
hashNode []byte hashNode []byte
valueNode []byte valueNode []byte
//fullnodeEncoder is a type used exclusively for encoding. Briefly instantiating // fullnodeEncoder is a type used exclusively for encoding fullNode.
// a fullnodeEncoder and initializing with existing slices is less memory // Briefly instantiating a fullnodeEncoder and initializing with
// intense than using the fullNode type. // existing slices is less memory intense than using the fullNode type.
fullnodeEncoder struct { fullnodeEncoder struct {
Children [17][]byte Children [17][]byte
} }
//shortNodeEncoder is a type used exclusively for encoding. Briefly instantiating // extNodeEncoder is a type used exclusively for encoding extension node.
// a shortNodeEncoder and initializing with existing slices is less memory // Briefly instantiating a extNodeEncoder and initializing with existing
// intense than using the shortNode type. // slices is less memory intense than using the shortNode type.
shortNodeEncoder struct { extNodeEncoder struct {
Key []byte
Val []byte
}
// leafNodeEncoder is a type used exclusively for encoding leaf node.
leafNodeEncoder struct {
Key []byte Key []byte
Val []byte Val []byte
} }

View file

@ -65,7 +65,7 @@ func (n *shortNode) encode(w rlp.EncoderBuffer) {
w.ListEnd(offset) w.ListEnd(offset)
} }
func (n *shortNodeEncoder) encode(w rlp.EncoderBuffer) { func (n *extNodeEncoder) encode(w rlp.EncoderBuffer) {
offset := w.List() offset := w.List()
w.WriteBytes(n.Key) w.WriteBytes(n.Key)
@ -79,6 +79,19 @@ func (n *shortNodeEncoder) encode(w rlp.EncoderBuffer) {
w.ListEnd(offset) w.ListEnd(offset)
} }
func (n *leafNodeEncoder) encode(w rlp.EncoderBuffer) {
offset := w.List()
// Encode the key to Compact format. The assumption is held
// that it's safe to modify the key in place.
n.Key = append(n.Key, 16) // termination flag
n.Key = hexToCompactInPlace(n.Key)
w.WriteBytes(n.Key) // Compact format key
w.WriteBytes(n.Val) // Value node, must be non-nil
w.ListEnd(offset)
}
func (n hashNode) encode(w rlp.EncoderBuffer) { func (n hashNode) encode(w rlp.EncoderBuffer) {
w.WriteBytes(n) w.WriteBytes(n)
} }

View file

@ -59,8 +59,17 @@ func NewStackTrie(onTrieNode OnTrieNode) *StackTrie {
root: stPool.Get().(*stNode), root: stPool.Get().(*stNode),
h: newHasher(false), h: newHasher(false),
onTrieNode: onTrieNode, onTrieNode: onTrieNode,
kBuf: make([]byte, 0, 64), kBuf: make([]byte, 64),
pBuf: make([]byte, 0, 32), pBuf: make([]byte, 64),
}
}
func (t *StackTrie) grow(key []byte) {
if cap(t.kBuf) < 2*len(key) {
t.kBuf = make([]byte, 2*len(key))
}
if cap(t.pBuf) < len(key) {
t.pBuf = make([]byte, 2*len(key))
} }
} }
@ -69,16 +78,8 @@ func (t *StackTrie) Update(key, value []byte) error {
if len(value) == 0 { if len(value) == 0 {
return errors.New("trying to insert empty (deletion)") return errors.New("trying to insert empty (deletion)")
} }
var k []byte t.grow(key)
{ // Need to expand the 'key' into hex-form. We use the dedicated buf for that. k := writeHexKey(t.kBuf, key)
if cap(t.kBuf) < 2*len(key) { // realloc to ensure sufficient cap
t.kBuf = make([]byte, 2*len(key))
}
// resize to ensure correct size
t.kBuf = t.kBuf[:2*len(key)]
writeHexKey(t.kBuf, key)
k = t.kBuf
}
if bytes.Compare(t.last, k) >= 0 { if bytes.Compare(t.last, k) >= 0 {
return errors.New("non-ascending key order") return errors.New("non-ascending key order")
} }
@ -171,6 +172,7 @@ func (n *stNode) getDiffIndex(key []byte) int {
} }
// Helper function to that inserts a (key, value) pair into the trie. // Helper function to that inserts a (key, value) pair into the trie.
//
// - The key is not retained by this method, but always copied if needed. // - The key is not retained by this method, but always copied if needed.
// - The value is retained by this method, as long as the leaf that it represents // - The value is retained by this method, as long as the leaf that it represents
// remains unhashed. However: it is never modified. // remains unhashed. However: it is never modified.
@ -306,7 +308,7 @@ func (t *StackTrie) insert(st *stNode, key, value []byte, path []byte) {
case emptyNode: /* Empty */ case emptyNode: /* Empty */
st.typ = leafNode st.typ = leafNode
st.key = append(st.key, key...) st.key = append(st.key, key...) // deep-copy the key as it's volatile
st.val = value st.val = value
case hashedNode: case hashedNode:
@ -364,7 +366,7 @@ func (t *StackTrie) hash(st *stNode, path []byte) {
t.hash(st.children[0], append(path, st.key...)) t.hash(st.children[0], append(path, st.key...))
// encode the extension node // encode the extension node
n := shortNodeEncoder{ n := extNodeEncoder{
Key: hexToCompactInPlace(st.key), Key: hexToCompactInPlace(st.key),
Val: st.children[0].val, Val: st.children[0].val,
} }
@ -375,14 +377,11 @@ func (t *StackTrie) hash(st *stNode, path []byte) {
st.children[0] = nil st.children[0] = nil
case leafNode: case leafNode:
st.key = append(st.key, byte(16)) n := leafNodeEncoder{
{ Key: st.key,
w := t.h.encbuf Val: st.val,
offset := w.List()
w.WriteBytes(hexToCompactInPlace(st.key))
w.WriteBytes(st.val)
w.ListEnd(offset)
} }
n.encode(t.h.encbuf)
blob = t.h.encodedBytes() blob = t.h.encodedBytes()
default: default:
@ -397,18 +396,14 @@ func (t *StackTrie) hash(st *stNode, path []byte) {
// Skip committing the non-root node if the size is smaller than 32 bytes // Skip committing the non-root node if the size is smaller than 32 bytes
// as tiny nodes are always embedded in their parent except root node. // as tiny nodes are always embedded in their parent except root node.
if len(blob) < 32 && len(path) > 0 { if len(blob) < 32 && len(path) > 0 {
val := bPool.Get() st.val = bPool.GetWithSize(len(blob))
val = val[:len(blob)] copy(st.val, blob)
copy(val, blob)
st.val = val
return return
} }
// Write the hash to the 'val'. We allocate a new val here to not mutate // Write the hash to the 'val'. We allocate a new val here to not mutate
// input values. // input values.
val := bPool.Get() st.val = bPool.GetWithSize(32)
val = val[:32] t.h.hashDataTo(st.val, blob)
t.h.hashDataTo(val, blob)
st.val = val
// Invoke the callback it's provided. Notably, the path and blob slices are // Invoke the callback it's provided. Notably, the path and blob slices are
// volatile, please deep-copy the slices in callback if the contents need // volatile, please deep-copy the slices in callback if the contents need