pathdb: improve perf by separating nodes map

remove comments

remove comments
This commit is contained in:
weiihann 2025-03-03 13:54:58 +08:00 committed by Gary Rong
parent d342f76232
commit 5afd54abb3

View file

@ -37,7 +37,8 @@ import (
// the combined trie node set from several aggregated state transitions. // the combined trie node set from several aggregated state transitions.
type nodeSet struct { type nodeSet struct {
size uint64 // aggregated size of the trie node size uint64 // aggregated size of the trie node
nodes map[common.Hash]map[string]*trienode.Node // node set, mapped by owner and path accountNodes map[string]*trienode.Node // account trie nodes, mapped by path
storageNodes map[common.Hash]map[string]*trienode.Node // storage trie nodes, mapped by owner and path
} }
// newNodeSet constructs the set with the provided dirty trie nodes. // newNodeSet constructs the set with the provided dirty trie nodes.
@ -46,7 +47,22 @@ func newNodeSet(nodes map[common.Hash]map[string]*trienode.Node) *nodeSet {
if nodes == nil { if nodes == nil {
nodes = make(map[common.Hash]map[string]*trienode.Node) nodes = make(map[common.Hash]map[string]*trienode.Node)
} }
s := &nodeSet{nodes: nodes}
// Create the new structure with separate maps
s := &nodeSet{
accountNodes: make(map[string]*trienode.Node),
storageNodes: make(map[common.Hash]map[string]*trienode.Node),
}
// Migrate the nodes to the appropriate maps
for owner, subset := range nodes {
if owner == (common.Hash{}) {
maps.Copy(s.accountNodes, subset)
} else {
s.storageNodes[owner] = maps.Clone(subset)
}
}
s.computeSize() s.computeSize()
return s return s
} }
@ -54,11 +70,13 @@ func newNodeSet(nodes map[common.Hash]map[string]*trienode.Node) *nodeSet {
// computeSize calculates the database size of the held trie nodes. // computeSize calculates the database size of the held trie nodes.
func (s *nodeSet) computeSize() { func (s *nodeSet) computeSize() {
var size uint64 var size uint64
for owner, subset := range s.nodes {
var prefix int for path, n := range s.accountNodes {
if owner != (common.Hash{}) { size += uint64(len(n.Blob) + len(path))
prefix = common.HashLength // owner (32 bytes) for storage trie nodes
} }
for _, subset := range s.storageNodes {
prefix := common.HashLength // owner (32 bytes) for storage trie nodes
for path, n := range subset { for path, n := range subset {
size += uint64(prefix + len(n.Blob) + len(path)) size += uint64(prefix + len(n.Blob) + len(path))
} }
@ -79,15 +97,19 @@ func (s *nodeSet) updateSize(delta int64) {
// node retrieves the trie node with node path and its trie identifier. // node retrieves the trie node with node path and its trie identifier.
func (s *nodeSet) node(owner common.Hash, path []byte) (*trienode.Node, bool) { func (s *nodeSet) node(owner common.Hash, path []byte) (*trienode.Node, bool) {
subset, ok := s.nodes[owner] if owner == (common.Hash{}) {
// Account trie node
n, ok := s.accountNodes[string(path)]
return n, ok
}
// Storage trie node
subset, ok := s.storageNodes[owner]
if !ok { if !ok {
return nil, false return nil, false
} }
n, ok := subset[string(path)] n, ok := subset[string(path)]
if !ok { return n, ok
return nil, false
}
return n, true
} }
// merge integrates the provided dirty nodes into the set. The provided nodeset // merge integrates the provided dirty nodes into the set. The provided nodeset
@ -97,12 +119,22 @@ func (s *nodeSet) merge(set *nodeSet) {
delta int64 // size difference resulting from node merging delta int64 // size difference resulting from node merging
overwrite counter // counter of nodes being overwritten overwrite counter // counter of nodes being overwritten
) )
for owner, subset := range set.nodes {
var prefix int // Merge account nodes
if owner != (common.Hash{}) { for path, n := range set.accountNodes {
prefix = common.HashLength if orig, exist := s.accountNodes[path]; !exist {
delta += int64(len(n.Blob) + len(path))
} else {
delta += int64(len(n.Blob) - len(orig.Blob))
overwrite.add(len(orig.Blob) + len(path))
} }
current, exist := s.nodes[owner] s.accountNodes[path] = n
}
// Merge storage nodes
for owner, subset := range set.storageNodes {
prefix := common.HashLength
current, exist := s.storageNodes[owner]
if !exist { if !exist {
for path, n := range subset { for path, n := range subset {
delta += int64(prefix + len(n.Blob) + len(path)) delta += int64(prefix + len(n.Blob) + len(path))
@ -113,7 +145,7 @@ func (s *nodeSet) merge(set *nodeSet) {
// accessible even after merging. Therefore, ownership of the nodes map // accessible even after merging. Therefore, ownership of the nodes map
// should still belong to the original layer, and any modifications to it // should still belong to the original layer, and any modifications to it
// should be prevented. // should be prevented.
s.nodes[owner] = maps.Clone(subset) s.storageNodes[owner] = maps.Clone(subset)
continue continue
} }
for path, n := range subset { for path, n := range subset {
@ -125,7 +157,7 @@ func (s *nodeSet) merge(set *nodeSet) {
} }
current[path] = n current[path] = n
} }
s.nodes[owner] = current s.storageNodes[owner] = current
} }
overwrite.report(gcTrieNodeMeter, gcTrieNodeBytesMeter) overwrite.report(gcTrieNodeMeter, gcTrieNodeBytesMeter)
s.updateSize(delta) s.updateSize(delta)
@ -136,27 +168,30 @@ func (s *nodeSet) merge(set *nodeSet) {
func (s *nodeSet) revertTo(db ethdb.KeyValueReader, nodes map[common.Hash]map[string]*trienode.Node) { func (s *nodeSet) revertTo(db ethdb.KeyValueReader, nodes map[common.Hash]map[string]*trienode.Node) {
var delta int64 var delta int64
for owner, subset := range nodes { for owner, subset := range nodes {
current, ok := s.nodes[owner] if owner == (common.Hash{}) {
// Account trie nodes
for path, n := range subset {
orig, ok := s.accountNodes[path]
if !ok {
blob := rawdb.ReadAccountTrieNode(db, []byte(path))
if bytes.Equal(blob, n.Blob) {
continue
}
panic(fmt.Sprintf("non-existent node (account %v) blob: %v", path, crypto.Keccak256Hash(n.Blob).Hex()))
}
s.accountNodes[path] = n
delta += int64(len(n.Blob)) - int64(len(orig.Blob))
}
} else {
// Storage trie nodes
current, ok := s.storageNodes[owner]
if !ok { if !ok {
panic(fmt.Sprintf("non-existent subset (%x)", owner)) panic(fmt.Sprintf("non-existent subset (%x)", owner))
} }
for path, n := range subset { for path, n := range subset {
orig, ok := current[path] orig, ok := current[path]
if !ok { if !ok {
// There is a special case in merkle tree that one child is removed blob := rawdb.ReadStorageTrieNode(db, owner, []byte(path))
// from a fullNode which only has two children, and then a new child
// with different position is immediately inserted into the fullNode.
// In this case, the clean child of the fullNode will also be marked
// as dirty because of node collapse and expansion. In case of database
// rollback, don't panic if this "clean" node occurs which is not
// present in buffer.
var blob []byte
if owner == (common.Hash{}) {
blob = rawdb.ReadAccountTrieNode(db, []byte(path))
} else {
blob = rawdb.ReadStorageTrieNode(db, owner, []byte(path))
}
// Ignore the clean node in the case described above.
if bytes.Equal(blob, n.Blob) { if bytes.Equal(blob, n.Blob) {
continue continue
} }
@ -166,6 +201,7 @@ func (s *nodeSet) revertTo(db ethdb.KeyValueReader, nodes map[common.Hash]map[st
delta += int64(len(n.Blob)) - int64(len(orig.Blob)) delta += int64(len(n.Blob)) - int64(len(orig.Blob))
} }
} }
}
s.updateSize(delta) s.updateSize(delta)
} }
@ -184,8 +220,22 @@ type journalNodes struct {
// encode serializes the content of trie nodes into the provided writer. // encode serializes the content of trie nodes into the provided writer.
func (s *nodeSet) encode(w io.Writer) error { func (s *nodeSet) encode(w io.Writer) error {
nodes := make([]journalNodes, 0, len(s.nodes)) nodes := make([]journalNodes, 0, len(s.storageNodes)+len(s.accountNodes))
for owner, subset := range s.nodes {
// Encode account nodes
if len(s.accountNodes) > 0 {
entry := journalNodes{Owner: common.Hash{}}
for path, node := range s.accountNodes {
entry.Nodes = append(entry.Nodes, journalNode{
Path: []byte(path),
Blob: node.Blob,
})
}
nodes = append(nodes, entry)
}
// Encode storage nodes
for owner, subset := range s.storageNodes {
entry := journalNodes{Owner: owner} entry := journalNodes{Owner: owner}
for path, node := range subset { for path, node := range subset {
entry.Nodes = append(entry.Nodes, journalNode{ entry.Nodes = append(entry.Nodes, journalNode{
@ -204,8 +254,24 @@ func (s *nodeSet) decode(r *rlp.Stream) error {
if err := r.Decode(&encoded); err != nil { if err := r.Decode(&encoded); err != nil {
return fmt.Errorf("load nodes: %v", err) return fmt.Errorf("load nodes: %v", err)
} }
nodes := make(map[common.Hash]map[string]*trienode.Node)
// Initialize the maps
s.accountNodes = make(map[string]*trienode.Node)
s.storageNodes = make(map[common.Hash]map[string]*trienode.Node)
// Decode the nodes
for _, entry := range encoded { for _, entry := range encoded {
if entry.Owner == (common.Hash{}) {
// Account nodes
for _, n := range entry.Nodes {
if len(n.Blob) > 0 {
s.accountNodes[string(n.Path)] = trienode.New(crypto.Keccak256Hash(n.Blob), n.Blob)
} else {
s.accountNodes[string(n.Path)] = trienode.NewDeleted()
}
}
} else {
// Storage nodes
subset := make(map[string]*trienode.Node) subset := make(map[string]*trienode.Node)
for _, n := range entry.Nodes { for _, n := range entry.Nodes {
if len(n.Blob) > 0 { if len(n.Blob) > 0 {
@ -214,33 +280,47 @@ func (s *nodeSet) decode(r *rlp.Stream) error {
subset[string(n.Path)] = trienode.NewDeleted() subset[string(n.Path)] = trienode.NewDeleted()
} }
} }
nodes[entry.Owner] = subset s.storageNodes[entry.Owner] = subset
}
} }
s.nodes = nodes
s.computeSize() s.computeSize()
return nil return nil
} }
// write flushes nodes into the provided database batch as a whole. // write flushes nodes into the provided database batch as a whole.
func (s *nodeSet) write(batch ethdb.Batch, clean *fastcache.Cache) int { func (s *nodeSet) write(batch ethdb.Batch, clean *fastcache.Cache) int {
return writeNodes(batch, s.nodes, clean) // Convert the separate maps back to the format expected by writeNodes
nodes := make(map[common.Hash]map[string]*trienode.Node)
// Add account nodes
if len(s.accountNodes) > 0 {
nodes[common.Hash{}] = s.accountNodes
}
// Add storage nodes
for owner, subset := range s.storageNodes {
nodes[owner] = subset
}
return writeNodes(batch, nodes, clean)
} }
// reset clears all cached trie node data. // reset clears all cached trie node data.
func (s *nodeSet) reset() { func (s *nodeSet) reset() {
s.nodes = make(map[common.Hash]map[string]*trienode.Node) s.accountNodes = make(map[string]*trienode.Node)
s.storageNodes = make(map[common.Hash]map[string]*trienode.Node)
s.size = 0 s.size = 0
} }
// dbsize returns the approximate size of db write. // dbsize returns the approximate size of db write.
func (s *nodeSet) dbsize() int { func (s *nodeSet) dbsize() int {
var m int var m int
for owner, nodes := range s.nodes {
if owner == (common.Hash{}) { m += len(s.accountNodes) * len(rawdb.TrieNodeAccountPrefix) // database key prefix
m += len(nodes) * len(rawdb.TrieNodeAccountPrefix) // database key prefix
} else { for _, nodes := range s.storageNodes {
m += len(nodes) * (len(rawdb.TrieNodeStoragePrefix)) // database key prefix m += len(nodes) * (len(rawdb.TrieNodeStoragePrefix)) // database key prefix
} }
}
return m + int(s.size) return m + int(s.size)
} }