trie: tweak cache formula to favour low-depth nodes

This change ensures that nodes closer to the root node are kept around
longer.
This commit is contained in:
Felix Lange 2016-10-19 15:06:56 +02:00
parent ca49510e6d
commit 766d71d7c2
5 changed files with 22 additions and 18 deletions

View file

@ -49,15 +49,20 @@ func returnHasherToPool(h *hasher) {
hasherPool.Put(h) hasherPool.Put(h)
} }
// canUnload decides whether a node can be unloaded.
func (n *nodeFlag) canUnload(cachegen, cachelimit uint16, depth int) bool {
return !n.dirty && cachegen-n.gen >= cachelimit/uint16(depth+1)
}
// hash collapses a node down into a hash node, also returning a copy of the // hash collapses a node down into a hash node, also returning a copy of the
// 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, depth int) (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 { if hash, dirty := n.cache(); hash != nil {
if db == nil { if db == nil {
return hash, n, nil return hash, n, nil
} }
if n.canUnload(h.cachegen, h.cachelimit) { if n.canUnload(h.cachegen, h.cachelimit, depth) {
// Unload the node from cache. All of its subnodes will have a lower or equal // Unload the node from cache. All of its subnodes will have a lower or equal
// cache generation number. // cache generation number.
return hash, hash, nil return hash, hash, nil
@ -67,7 +72,7 @@ func (h *hasher) hash(n node, db DatabaseWriter, force bool) (node, node, error)
} }
} }
// 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, depth)
if err != nil { if err != nil {
return hashNode{}, n, err return hashNode{}, n, err
} }
@ -97,7 +102,7 @@ func (h *hasher) hash(n node, db DatabaseWriter, force bool) (node, node, error)
// hashChildren replaces the children of a node with their hashes if the encoded // hashChildren replaces the children of a node with their hashes if the encoded
// size of the child is larger than a hash, returning the collapsed node as well // size of the child is larger than a hash, returning the collapsed node as well
// as a replacement for the original node with the child hashes cached in. // as a replacement for the original node with the child hashes cached in.
func (h *hasher) hashChildren(original node, db DatabaseWriter) (node, node, error) { func (h *hasher) hashChildren(original node, db DatabaseWriter, depth int) (node, node, error) {
var err error var err error
switch n := original.(type) { switch n := original.(type) {
@ -108,7 +113,7 @@ func (h *hasher) hashChildren(original node, db DatabaseWriter) (node, node, err
cached.Key = common.CopyBytes(n.Key) cached.Key = common.CopyBytes(n.Key)
if _, ok := n.Val.(valueNode); !ok { if _, ok := n.Val.(valueNode); !ok {
collapsed.Val, cached.Val, err = h.hash(n.Val, db, false) collapsed.Val, cached.Val, err = h.hash(n.Val, db, false, depth+1)
if err != nil { if err != nil {
return original, original, err return original, original, err
} }
@ -124,7 +129,7 @@ func (h *hasher) hashChildren(original node, db DatabaseWriter) (node, node, err
for i := 0; i < 16; i++ { for i := 0; i < 16; i++ {
if n.Children[i] != nil { if n.Children[i] != nil {
collapsed.Children[i], cached.Children[i], err = h.hash(n.Children[i], db, false) collapsed.Children[i], cached.Children[i], err = h.hash(n.Children[i], db, false, depth+1)
if err != nil { if err != nil {
return original, original, err return original, original, err
} }

View file

@ -30,7 +30,7 @@ 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 canUnload(cachegen, cachelimit uint16, depth int) bool
} }
type ( type (
@ -62,15 +62,14 @@ type nodeFlag struct {
dirty bool // whether the node has changes that must be written to the database dirty bool // whether the node has changes that must be written to the database
} }
// canUnload tells whether a node can be unloaded. func (n *fullNode) canUnload(gen, limit uint16, depth int) bool {
func (n *nodeFlag) canUnload(cachegen, cachelimit uint16) bool { return n.flags.canUnload(gen, limit, depth)
return !n.dirty && cachegen-n.gen >= cachelimit
} }
func (n *shortNode) canUnload(gen, limit uint16, depth int) bool {
func (n *fullNode) canUnload(gen, limit uint16) bool { return n.flags.canUnload(gen, limit) } return n.flags.canUnload(gen, limit, depth)
func (n *shortNode) canUnload(gen, limit uint16) bool { return n.flags.canUnload(gen, limit) } }
func (n hashNode) canUnload(uint16, uint16) bool { return false } func (n hashNode) canUnload(uint16, uint16, int) bool { return false }
func (n valueNode) canUnload(uint16, uint16) bool { return false } func (n valueNode) canUnload(uint16, uint16, int) bool { return false }
func (n *fullNode) cache() (hashNode, bool) { return n.flags.hash, n.flags.dirty } func (n *fullNode) cache() (hashNode, bool) { return n.flags.hash, n.flags.dirty }
func (n *shortNode) cache() (hashNode, bool) { return n.flags.hash, n.flags.dirty } func (n *shortNode) cache() (hashNode, bool) { return n.flags.hash, n.flags.dirty }

View file

@ -51,7 +51,7 @@ func TestCanUnload(t *testing.T) {
} }
for _, test := range tests { for _, test := range tests {
if got := test.flag.canUnload(test.cachegen, test.cachelimit); got != test.want { if got := test.flag.canUnload(test.cachegen, test.cachelimit, 0); got != test.want {
t.Errorf("%+v\n got %t, want %t", test, got, test.want) t.Errorf("%+v\n got %t, want %t", test, got, test.want)
} }
} }

View file

@ -75,7 +75,7 @@ func (t *Trie) Prove(key []byte) []rlp.RawValue {
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
// if encoding doesn't work and we're not writing to any database. // if encoding doesn't work and we're not writing to any database.
n, _, _ = hasher.hashChildren(n, nil) n, _, _ = hasher.hashChildren(n, nil, 0)
hn, _ := hasher.store(n, nil, false) hn, _ := hasher.store(n, nil, false)
if _, ok := hn.(hashNode); ok || i == 0 { if _, ok := hn.(hashNode); ok || i == 0 {
// If the node's database encoding is a hash (or is the // If the node's database encoding is a hash (or is the

View file

@ -495,5 +495,5 @@ func (t *Trie) hashRoot(db DatabaseWriter) (node, node, error) {
} }
h := newHasher(t.cachegen, t.cachelimit) 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, 0)
} }