mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 06:36:43 +00:00
trie: unload old cached nodes
This commit is contained in:
parent
36e4f76cad
commit
c4568fb625
5 changed files with 80 additions and 33 deletions
|
|
@ -27,8 +27,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type hasher struct {
|
type hasher struct {
|
||||||
tmp *bytes.Buffer
|
tmp *bytes.Buffer
|
||||||
sha hash.Hash
|
sha hash.Hash
|
||||||
|
cachegen, cachelimit uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
// hashers live in a global pool.
|
// hashers live in a global pool.
|
||||||
|
|
@ -38,8 +39,10 @@ var hasherPool = sync.Pool{
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHasher() *hasher {
|
func newHasher(cachegen, cachelimit uint16) *hasher {
|
||||||
return hasherPool.Get().(*hasher)
|
h := hasherPool.Get().(*hasher)
|
||||||
|
h.cachegen, h.cachelimit = cachegen, cachelimit
|
||||||
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
func returnHasherToPool(h *hasher) {
|
func returnHasherToPool(h *hasher) {
|
||||||
|
|
@ -50,8 +53,16 @@ func returnHasherToPool(h *hasher) {
|
||||||
// original node initialzied with the computed hash to replace the original one.
|
// original node initialzied with the computed hash to replace the original one.
|
||||||
func (h *hasher) hash(n node, db DatabaseWriter, force bool) (node, node, error) {
|
func (h *hasher) hash(n node, db DatabaseWriter, force bool) (node, node, error) {
|
||||||
// If we're not storing the node, just hashing, use avaialble cached data
|
// If we're not storing the node, just hashing, use avaialble cached data
|
||||||
if hash, dirty := n.cache(); hash != nil && (db == nil || !dirty) {
|
if hash, dirty := n.cache(); hash != nil {
|
||||||
return hash, n, nil
|
if db == nil {
|
||||||
|
return hash, n, nil
|
||||||
|
}
|
||||||
|
if n.canUnload(h.cachegen, h.cachelimit) {
|
||||||
|
return hash, hash, nil
|
||||||
|
}
|
||||||
|
if !dirty {
|
||||||
|
return hash, n, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Trie not processed yet or needs storage, walk the children
|
// Trie not processed yet or needs storage, walk the children
|
||||||
collapsed, cached, err := h.hashChildren(n, db)
|
collapsed, cached, err := h.hashChildren(n, db)
|
||||||
|
|
@ -110,8 +121,7 @@ func (h *hasher) hashChildren(original node, db DatabaseWriter) (node, node, err
|
||||||
|
|
||||||
case *fullNode:
|
case *fullNode:
|
||||||
// Hash the full node's children, caching the newly hashed subtrees
|
// Hash the full node's children, caching the newly hashed subtrees
|
||||||
collapsed := n.copy()
|
collapsed, cached := n.copy(), n.copy()
|
||||||
cached := &fullNode{dirty: n.dirty}
|
|
||||||
|
|
||||||
for i := 0; i < 16; i++ {
|
for i := 0; i < 16; i++ {
|
||||||
if n.Children[i] != nil {
|
if n.Children[i] != nil {
|
||||||
|
|
|
||||||
47
trie/node.go
47
trie/node.go
|
|
@ -30,19 +30,18 @@ var indices = []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b
|
||||||
type node interface {
|
type node interface {
|
||||||
fstring(string) string
|
fstring(string) string
|
||||||
cache() (hashNode, bool)
|
cache() (hashNode, bool)
|
||||||
|
canUnload(cachegen, cachelimit uint16) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
fullNode struct {
|
fullNode struct {
|
||||||
Children [17]node // Actual trie node data to encode/decode (needs custom encoder)
|
Children [17]node // Actual trie node data to encode/decode (needs custom encoder)
|
||||||
hash hashNode // Cached hash of the node to prevent rehashing (may be nil)
|
nodeFlag
|
||||||
dirty bool // Cached flag whether the node's new or already stored
|
|
||||||
}
|
}
|
||||||
shortNode struct {
|
shortNode struct {
|
||||||
Key []byte
|
Key []byte
|
||||||
Val node
|
Val node
|
||||||
hash hashNode // Cached hash of the node to prevent rehashing (may be nil)
|
nodeFlag
|
||||||
dirty bool // Cached flag whether the node's new or already stored
|
|
||||||
}
|
}
|
||||||
hashNode []byte
|
hashNode []byte
|
||||||
valueNode []byte
|
valueNode []byte
|
||||||
|
|
@ -56,11 +55,30 @@ func (n *fullNode) EncodeRLP(w io.Writer) error {
|
||||||
func (n *fullNode) copy() *fullNode { copy := *n; return © }
|
func (n *fullNode) copy() *fullNode { copy := *n; return © }
|
||||||
func (n *shortNode) copy() *shortNode { copy := *n; return © }
|
func (n *shortNode) copy() *shortNode { copy := *n; return © }
|
||||||
|
|
||||||
// Cache accessors to retrieve precalculated values (avoid lengthy type switches).
|
// nodeFlag contains caching-related metadata about a node.
|
||||||
func (n *fullNode) cache() (hashNode, bool) { return n.hash, n.dirty }
|
type nodeFlag struct {
|
||||||
func (n *shortNode) cache() (hashNode, bool) { return n.hash, n.dirty }
|
hash hashNode // cached hash of the node (may be nil)
|
||||||
func (n hashNode) cache() (hashNode, bool) { return nil, true }
|
gen uint16 // cache generation counter
|
||||||
func (n valueNode) cache() (hashNode, bool) { return nil, true }
|
dirty bool // whether the node has changes that must be written to the database
|
||||||
|
}
|
||||||
|
|
||||||
|
// canUnload tells whether a node can be unloaded.
|
||||||
|
func (n nodeFlag) canUnload(cachegen, cachelimit uint16) bool {
|
||||||
|
var dist uint16
|
||||||
|
if n.gen > cachegen {
|
||||||
|
dist = n.gen - cachegen
|
||||||
|
} else {
|
||||||
|
dist = cachegen - n.gen
|
||||||
|
}
|
||||||
|
return !n.dirty && dist > cachelimit
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n hashNode) canUnload(uint16, uint16) bool { return false }
|
||||||
|
func (n valueNode) canUnload(uint16, uint16) bool { return false }
|
||||||
|
|
||||||
|
func (n nodeFlag) cache() (hashNode, bool) { return n.hash, n.dirty }
|
||||||
|
func (n hashNode) cache() (hashNode, bool) { return nil, true }
|
||||||
|
func (n valueNode) cache() (hashNode, bool) { return nil, true }
|
||||||
|
|
||||||
// Pretty printing.
|
// Pretty printing.
|
||||||
func (n *fullNode) String() string { return n.fstring("") }
|
func (n *fullNode) String() string { return n.fstring("") }
|
||||||
|
|
@ -123,6 +141,7 @@ func decodeShort(hash, buf, elems []byte) (node, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
flag := nodeFlag{hash: hash}
|
||||||
key := compactDecode(kbuf)
|
key := compactDecode(kbuf)
|
||||||
if key[len(key)-1] == 16 {
|
if key[len(key)-1] == 16 {
|
||||||
// value node
|
// value node
|
||||||
|
|
@ -130,17 +149,17 @@ func decodeShort(hash, buf, elems []byte) (node, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid value node: %v", err)
|
return nil, fmt.Errorf("invalid value node: %v", err)
|
||||||
}
|
}
|
||||||
return &shortNode{key, append(valueNode{}, val...), hash, false}, nil
|
return &shortNode{key, append(valueNode{}, val...), flag}, nil
|
||||||
}
|
}
|
||||||
r, _, err := decodeRef(rest)
|
r, _, err := decodeRef(rest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, wrapError(err, "val")
|
return nil, wrapError(err, "val")
|
||||||
}
|
}
|
||||||
return &shortNode{key, r, hash, false}, nil
|
return &shortNode{key, r, flag}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeFull(hash, buf, elems []byte) (*fullNode, error) {
|
func decodeFull(hash, buf, elems []byte) (*fullNode, error) {
|
||||||
n := &fullNode{hash: hash}
|
n := &fullNode{nodeFlag: nodeFlag{hash: hash}}
|
||||||
for i := 0; i < 16; i++ {
|
for i := 0; i < 16; i++ {
|
||||||
cld, rest, err := decodeRef(elems)
|
cld, rest, err := decodeRef(elems)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ func (t *Trie) Prove(key []byte) []rlp.RawValue {
|
||||||
panic(fmt.Sprintf("%T: invalid node: %v", tn, tn))
|
panic(fmt.Sprintf("%T: invalid node: %v", tn, tn))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
hasher := newHasher()
|
hasher := newHasher(0, 0)
|
||||||
proof := make([]rlp.RawValue, 0, len(nodes))
|
proof := make([]rlp.RawValue, 0, len(nodes))
|
||||||
for i, n := range nodes {
|
for i, n := range nodes {
|
||||||
// Don't bother checking for errors here since hasher panics
|
// Don't bother checking for errors here since hasher panics
|
||||||
|
|
|
||||||
|
|
@ -191,7 +191,7 @@ func (t *SecureTrie) secKey(key []byte) []byte {
|
||||||
// The caller must not hold onto the return value because it will become
|
// The caller must not hold onto the return value because it will become
|
||||||
// invalid on the next call to hashKey or secKey.
|
// invalid on the next call to hashKey or secKey.
|
||||||
func (t *SecureTrie) hashKey(key []byte) []byte {
|
func (t *SecureTrie) hashKey(key []byte) []byte {
|
||||||
h := newHasher()
|
h := newHasher(0, 0)
|
||||||
h.sha.Reset()
|
h.sha.Reset()
|
||||||
h.sha.Write(key)
|
h.sha.Write(key)
|
||||||
buf := h.sha.Sum(t.hashKeyBuf[:0])
|
buf := h.sha.Sum(t.hashKeyBuf[:0])
|
||||||
|
|
|
||||||
36
trie/trie.go
36
trie/trie.go
|
|
@ -62,6 +62,23 @@ type Trie struct {
|
||||||
root node
|
root node
|
||||||
db Database
|
db Database
|
||||||
originalRoot common.Hash
|
originalRoot common.Hash
|
||||||
|
|
||||||
|
// Cache generation values.
|
||||||
|
// cachegen increase by one with each commit operation.
|
||||||
|
// new nodes are tagged with the current generation and unloaded
|
||||||
|
// when their generation is older than than cachegen-cachelimit.
|
||||||
|
cachegen, cachelimit uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCacheLimit sets the number of 'cache generations' to keep.
|
||||||
|
// A cache generations is created by a call to Commit.
|
||||||
|
func (t *Trie) SetCacheLimit(l uint16) {
|
||||||
|
t.cachelimit = l
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFlag returns the cache flag value for a newly created node.
|
||||||
|
func (t *Trie) newFlag() nodeFlag {
|
||||||
|
return nodeFlag{dirty: true, gen: t.cachegen}
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a trie with an existing root node from db.
|
// New creates a trie with an existing root node from db.
|
||||||
|
|
@ -206,10 +223,10 @@ func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error
|
||||||
if !dirty || err != nil {
|
if !dirty || err != nil {
|
||||||
return false, n, err
|
return false, n, err
|
||||||
}
|
}
|
||||||
return true, &shortNode{n.Key, nn, nil, true}, nil
|
return true, &shortNode{n.Key, nn, t.newFlag()}, nil
|
||||||
}
|
}
|
||||||
// Otherwise branch out at the index where they differ.
|
// Otherwise branch out at the index where they differ.
|
||||||
branch := &fullNode{dirty: true}
|
branch := &fullNode{nodeFlag: t.newFlag()}
|
||||||
var err error
|
var err error
|
||||||
_, branch.Children[n.Key[matchlen]], err = t.insert(nil, append(prefix, n.Key[:matchlen+1]...), n.Key[matchlen+1:], n.Val)
|
_, branch.Children[n.Key[matchlen]], err = t.insert(nil, append(prefix, n.Key[:matchlen+1]...), n.Key[matchlen+1:], n.Val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -224,7 +241,7 @@ func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error
|
||||||
return true, branch, nil
|
return true, branch, nil
|
||||||
}
|
}
|
||||||
// Otherwise, replace it with a short node leading up to the branch.
|
// Otherwise, replace it with a short node leading up to the branch.
|
||||||
return true, &shortNode{key[:matchlen], branch, nil, true}, nil
|
return true, &shortNode{key[:matchlen], branch, t.newFlag()}, nil
|
||||||
|
|
||||||
case *fullNode:
|
case *fullNode:
|
||||||
dirty, nn, err := t.insert(n.Children[key[0]], append(prefix, key[0]), key[1:], value)
|
dirty, nn, err := t.insert(n.Children[key[0]], append(prefix, key[0]), key[1:], value)
|
||||||
|
|
@ -236,7 +253,7 @@ func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error
|
||||||
return true, n, nil
|
return true, n, nil
|
||||||
|
|
||||||
case nil:
|
case nil:
|
||||||
return true, &shortNode{key, value, nil, true}, nil
|
return true, &shortNode{key, value, t.newFlag()}, nil
|
||||||
|
|
||||||
case hashNode:
|
case hashNode:
|
||||||
// We've hit a part of the trie that isn't loaded yet. Load
|
// We've hit a part of the trie that isn't loaded yet. Load
|
||||||
|
|
@ -305,9 +322,9 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) {
|
||||||
// always creates a new slice) instead of append to
|
// always creates a new slice) instead of append to
|
||||||
// avoid modifying n.Key since it might be shared with
|
// avoid modifying n.Key since it might be shared with
|
||||||
// other nodes.
|
// other nodes.
|
||||||
return true, &shortNode{concat(n.Key, child.Key...), child.Val, nil, true}, nil
|
return true, &shortNode{concat(n.Key, child.Key...), child.Val, t.newFlag()}, nil
|
||||||
default:
|
default:
|
||||||
return true, &shortNode{n.Key, child, nil, true}, nil
|
return true, &shortNode{n.Key, child, t.newFlag()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
case *fullNode:
|
case *fullNode:
|
||||||
|
|
@ -352,12 +369,12 @@ func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) {
|
||||||
}
|
}
|
||||||
if cnode, ok := cnode.(*shortNode); ok {
|
if cnode, ok := cnode.(*shortNode); ok {
|
||||||
k := append([]byte{byte(pos)}, cnode.Key...)
|
k := append([]byte{byte(pos)}, cnode.Key...)
|
||||||
return true, &shortNode{k, cnode.Val, nil, true}, nil
|
return true, &shortNode{k, cnode.Val, t.newFlag()}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Otherwise, n is replaced by a one-nibble short node
|
// Otherwise, n is replaced by a one-nibble short node
|
||||||
// containing the child.
|
// containing the child.
|
||||||
return true, &shortNode{[]byte{byte(pos)}, n.Children[pos], nil, true}, nil
|
return true, &shortNode{[]byte{byte(pos)}, n.Children[pos], t.newFlag()}, nil
|
||||||
}
|
}
|
||||||
// n still contains at least two values and cannot be reduced.
|
// n still contains at least two values and cannot be reduced.
|
||||||
return true, n, nil
|
return true, n, nil
|
||||||
|
|
@ -453,6 +470,7 @@ func (t *Trie) CommitTo(db DatabaseWriter) (root common.Hash, err error) {
|
||||||
return (common.Hash{}), err
|
return (common.Hash{}), err
|
||||||
}
|
}
|
||||||
t.root = cached
|
t.root = cached
|
||||||
|
t.cachegen++
|
||||||
return common.BytesToHash(hash.(hashNode)), nil
|
return common.BytesToHash(hash.(hashNode)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -460,7 +478,7 @@ func (t *Trie) hashRoot(db DatabaseWriter) (node, node, error) {
|
||||||
if t.root == nil {
|
if t.root == nil {
|
||||||
return hashNode(emptyRoot.Bytes()), nil, nil
|
return hashNode(emptyRoot.Bytes()), nil, nil
|
||||||
}
|
}
|
||||||
h := newHasher()
|
h := newHasher(t.cachegen, t.cachelimit)
|
||||||
defer returnHasherToPool(h)
|
defer returnHasherToPool(h)
|
||||||
return h.hash(t.root, db, true)
|
return h.hash(t.root, db, true)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue