trie: if insertion/deletion is noop, don't expand hash nodes

This commit is contained in:
Péter Szilágyi 2016-05-13 11:16:55 +03:00
parent 72e60dea36
commit bc432f8102

View file

@ -169,13 +169,13 @@ func (t *Trie) Update(key, value []byte) {
func (t *Trie) TryUpdate(key, value []byte) error { func (t *Trie) TryUpdate(key, value []byte) error {
k := compactHexDecode(key) k := compactHexDecode(key)
if len(value) != 0 { if len(value) != 0 {
n, err := t.insert(t.root, nil, k, valueNode(value)) _, n, err := t.insert(t.root, nil, k, valueNode(value))
if err != nil { if err != nil {
return err return err
} }
t.root = n t.root = n
} else { } else {
n, err := t.delete(t.root, nil, k) _, n, err := t.delete(t.root, nil, k)
if err != nil { if err != nil {
return err return err
} }
@ -184,9 +184,13 @@ func (t *Trie) TryUpdate(key, value []byte) error {
return nil return nil
} }
func (t *Trie) insert(n node, prefix, key []byte, value node) (node, error) { func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error) {
if len(key) == 0 { if len(key) == 0 {
return value, nil if v, ok := n.(valueNode); ok {
vv := value.(valueNode)
return len(v) != len(vv) || bytes.Compare(v, vv) != 0, value, nil
}
return true, value, nil
} }
switch n := n.(type) { switch n := n.(type) {
case shortNode: case shortNode:
@ -194,53 +198,63 @@ func (t *Trie) insert(n node, prefix, key []byte, value node) (node, error) {
// If the whole key matches, keep this short node as is // If the whole key matches, keep this short node as is
// and only update the value. // and only update the value.
if matchlen == len(n.Key) { if matchlen == len(n.Key) {
nn, err := t.insert(n.Val, append(prefix, key[:matchlen]...), key[matchlen:], value) changed, nn, err := t.insert(n.Val, append(prefix, key[:matchlen]...), key[matchlen:], value)
if err != nil { if err != nil {
return nil, err return false, nil, err
} }
return shortNode{n.Key, nn}, nil if !changed {
return false, n, nil
}
return true, shortNode{n.Key, nn}, nil
} }
// Otherwise branch out at the index where they differ. // Otherwise branch out at the index where they differ.
var branch fullNode var branch fullNode
var err error var err error
branch[n.Key[matchlen]], err = t.insert(nil, append(prefix, n.Key[:matchlen+1]...), n.Key[matchlen+1:], n.Val) _, branch[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 {
return nil, err return false, nil, err
} }
branch[key[matchlen]], err = t.insert(nil, append(prefix, key[:matchlen+1]...), key[matchlen+1:], value) _, branch[key[matchlen]], err = t.insert(nil, append(prefix, key[:matchlen+1]...), key[matchlen+1:], value)
if err != nil { if err != nil {
return nil, err return false, nil, err
} }
// Replace this shortNode with the branch if it occurs at index 0. // Replace this shortNode with the branch if it occurs at index 0.
if matchlen == 0 { if matchlen == 0 {
return 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 shortNode{key[:matchlen], branch}, nil return true, shortNode{key[:matchlen], branch}, nil
case fullNode: case fullNode:
nn, err := t.insert(n[key[0]], append(prefix, key[0]), key[1:], value) changed, nn, err := t.insert(n[key[0]], append(prefix, key[0]), key[1:], value)
if err != nil { if err != nil {
return nil, err return false, nil, err
}
if !changed {
return false, n, nil
} }
n[key[0]] = nn n[key[0]] = nn
return n, nil return true, n, nil
case nil: case nil:
return shortNode{key, value}, nil return true, shortNode{key, value}, 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
// the node and insert into it. This leaves all child nodes on // the node and insert into it. This leaves all child nodes on
// the path to the value in the trie. // the path to the value in the trie.
//
// TODO: track whether insertion changed the value and keep
// n as a hash node if it didn't.
rn, err := t.resolveHash(n, prefix, key) rn, err := t.resolveHash(n, prefix, key)
if err != nil { if err != nil {
return nil, err return false, nil, err
} }
return t.insert(rn, prefix, key, value) changed, nn, err := t.insert(rn, prefix, key, value)
if err != nil {
return false, nil, err
}
if !changed {
return false, n, nil
}
return true, nn, nil
default: default:
panic(fmt.Sprintf("%T: invalid node: %v", n, n)) panic(fmt.Sprintf("%T: invalid node: %v", n, n))
@ -258,7 +272,7 @@ func (t *Trie) Delete(key []byte) {
// If a node was not found in the database, a MissingNodeError is returned. // If a node was not found in the database, a MissingNodeError is returned.
func (t *Trie) TryDelete(key []byte) error { func (t *Trie) TryDelete(key []byte) error {
k := compactHexDecode(key) k := compactHexDecode(key)
n, err := t.delete(t.root, nil, k) _, n, err := t.delete(t.root, nil, k)
if err != nil { if err != nil {
return err return err
} }
@ -269,23 +283,26 @@ func (t *Trie) TryDelete(key []byte) error {
// delete returns the new root of the trie with key deleted. // delete returns the new root of the trie with key deleted.
// It reduces the trie to minimal form by simplifying // It reduces the trie to minimal form by simplifying
// nodes on the way up after deleting recursively. // nodes on the way up after deleting recursively.
func (t *Trie) delete(n node, prefix, key []byte) (node, error) { func (t *Trie) delete(n node, prefix, key []byte) (bool, node, error) {
switch n := n.(type) { switch n := n.(type) {
case shortNode: case shortNode:
matchlen := prefixLen(key, n.Key) matchlen := prefixLen(key, n.Key)
if matchlen < len(n.Key) { if matchlen < len(n.Key) {
return n, nil // don't replace n on mismatch return false, n, nil // don't replace n on mismatch
} }
if matchlen == len(key) { if matchlen == len(key) {
return nil, nil // remove n entirely for whole matches return true, nil, nil // remove n entirely for whole matches
} }
// The key is longer than n.Key. Remove the remaining suffix // The key is longer than n.Key. Remove the remaining suffix
// from the subtrie. Child can never be nil here since the // from the subtrie. Child can never be nil here since the
// subtrie must contain at least two other values with keys // subtrie must contain at least two other values with keys
// longer than n.Key. // longer than n.Key.
child, err := t.delete(n.Val, append(prefix, key[:len(n.Key)]...), key[len(n.Key):]) change, child, err := t.delete(n.Val, append(prefix, key[:len(n.Key)]...), key[len(n.Key):])
if err != nil { if err != nil {
return nil, err return false, nil, err
}
if !change {
return false, n, nil
} }
switch child := child.(type) { switch child := child.(type) {
case shortNode: case shortNode:
@ -295,15 +312,18 @@ func (t *Trie) delete(n node, prefix, key []byte) (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 shortNode{concat(n.Key, child.Key...), child.Val}, nil return true, shortNode{concat(n.Key, child.Key...), child.Val}, nil
default: default:
return shortNode{n.Key, child}, nil return true, shortNode{n.Key, child}, nil
} }
case fullNode: case fullNode:
nn, err := t.delete(n[key[0]], append(prefix, key[0]), key[1:]) change, nn, err := t.delete(n[key[0]], append(prefix, key[0]), key[1:])
if err != nil { if err != nil {
return nil, err return false, nil, err
}
if !change {
return false, n, nil
} }
n[key[0]] = nn n[key[0]] = nn
// Check how many non-nil entries are left after deleting and // Check how many non-nil entries are left after deleting and
@ -336,22 +356,22 @@ func (t *Trie) delete(n node, prefix, key []byte) (node, error) {
// check. // check.
cnode, err := t.resolve(n[pos], prefix, []byte{byte(pos)}) cnode, err := t.resolve(n[pos], prefix, []byte{byte(pos)})
if err != nil { if err != nil {
return nil, err return false, nil, err
} }
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 shortNode{k, cnode.Val}, nil return true, shortNode{k, cnode.Val}, 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 shortNode{[]byte{byte(pos)}, n[pos]}, nil return true, shortNode{[]byte{byte(pos)}, n[pos]}, nil
} }
// n still contains at least two values and cannot be reduced. // n still contains at least two values and cannot be reduced.
return n, nil return true, n, nil
case nil: case nil:
return nil, nil return false, nil, 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
@ -362,9 +382,16 @@ func (t *Trie) delete(n node, prefix, key []byte) (node, error) {
// n as a hash node if it didn't. // n as a hash node if it didn't.
rn, err := t.resolveHash(n, prefix, key) rn, err := t.resolveHash(n, prefix, key)
if err != nil { if err != nil {
return nil, err return false, nil, err
} }
return t.delete(rn, prefix, key) change, nn, err := t.delete(rn, prefix, key)
if err != nil {
return false, nil, err
}
if !change {
return false, n, nil
}
return true, nn, nil
default: default:
panic(fmt.Sprintf("%T: invalid node: %v (%v)", n, n, key)) panic(fmt.Sprintf("%T: invalid node: %v (%v)", n, n, key))