Skip to content

Commit 2f133e9

Browse files
fjlsplix
authored andcommitted
trie, core/state: improve memory usage and performance (#3135)
* trie: store nodes as pointers This avoids memory copies when unwrapping node interface values. name old time/op new time/op delta Get 388ns ± 8% 215ns ± 2% -44.56% (p=0.000 n=15+15) GetDB 363ns ± 3% 202ns ± 2% -44.21% (p=0.000 n=15+15) UpdateBE 1.57µs ± 2% 1.29µs ± 3% -17.80% (p=0.000 n=13+15) UpdateLE 1.92µs ± 2% 1.61µs ± 2% -16.25% (p=0.000 n=14+14) HashBE 2.16µs ± 6% 2.18µs ± 6% ~ (p=0.436 n=15+15) HashLE 7.43µs ± 3% 7.21µs ± 3% -2.96% (p=0.000 n=15+13) * trie: close temporary databases in GetDB benchmark * trie: don't keep []byte from DB load around Nodes decoded from a DB load kept hashes and values as sub-slices of the DB value. This can be a problem because loading from leveldb often returns []byte with a cap that's larger than necessary, increasing memory usage. * trie: unload old cached nodes * trie, core/state: use cache unloading for account trie * trie: use explicit private flags (fixes Go 1.5 reflection issue). * trie: fixup cachegen overflow at request of nick * core/state: rename journal size constant
1 parent 4bea849 commit 2f133e9

15 files changed

+249
-142
lines changed

core/blockchain.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ func (self *BlockChain) FastSyncCommitHead(hash common.Hash) error {
269269
if block == nil {
270270
return fmt.Errorf("non existent block [%x…]", hash[:4])
271271
}
272-
if _, err := trie.NewSecure(block.Root(), self.chainDb); err != nil {
272+
if _, err := trie.NewSecure(block.Root(), self.chainDb, 0); err != nil {
273273
return err
274274
}
275275
// If all checks out, manually set the head block

core/state/state_object.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,9 @@ func (self *StateObject) markSuicided() {
137137
func (c *StateObject) getTrie(db trie.Database) *trie.SecureTrie {
138138
if c.trie == nil {
139139
var err error
140-
c.trie, err = trie.NewSecure(c.data.Root, db)
140+
c.trie, err = trie.NewSecure(c.data.Root, db, 0)
141141
if err != nil {
142-
c.trie, _ = trie.NewSecure(common.Hash{}, db)
142+
c.trie, _ = trie.NewSecure(common.Hash{}, db, 0)
143143
c.setError(fmt.Errorf("can't create storage trie: %v", err))
144144
}
145145
}

core/state/statedb.go

+7-4
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ var StartingNonce uint64
4141
const (
4242
// Number of past tries to keep. The arbitrarily chosen value here
4343
// is max uncle depth + 1.
44-
maxTrieCacheLength = 8
44+
maxPastTries = 8
45+
46+
// Trie cache generation limit.
47+
maxTrieCacheGen = 100
4548

4649
// Number of codehash->size associations to keep.
4750
codeSizeCacheSize = 100000
@@ -86,7 +89,7 @@ type StateDB struct {
8689

8790
// Create a new state from a given trie
8891
func New(root common.Hash, db ethdb.Database) (*StateDB, error) {
89-
tr, err := trie.NewSecure(root, db)
92+
tr, err := trie.NewSecure(root, db, maxTrieCacheGen)
9093
if err != nil {
9194
return nil, err
9295
}
@@ -155,14 +158,14 @@ func (self *StateDB) openTrie(root common.Hash) (*trie.SecureTrie, error) {
155158
return &tr, nil
156159
}
157160
}
158-
return trie.NewSecure(root, self.db)
161+
return trie.NewSecure(root, self.db, maxTrieCacheGen)
159162
}
160163

161164
func (self *StateDB) pushTrie(t *trie.SecureTrie) {
162165
self.lock.Lock()
163166
defer self.lock.Unlock()
164167

165-
if len(self.pastTries) >= maxTrieCacheLength {
168+
if len(self.pastTries) >= maxPastTries {
166169
copy(self.pastTries, self.pastTries[1:])
167170
self.pastTries[len(self.pastTries)-1] = t
168171
} else {

eth/downloader/downloader_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ func (dl *downloadTester) headFastBlock() *types.Block {
286286
func (dl *downloadTester) commitHeadBlock(hash common.Hash) error {
287287
// For now only check that the state trie is correct
288288
if block := dl.getBlock(hash); block != nil {
289-
_, err := trie.NewSecure(block.Root(), dl.stateDb)
289+
_, err := trie.NewSecure(block.Root(), dl.stateDb, 0)
290290
return err
291291
}
292292
return fmt.Errorf("non existent block: %x", hash[:4])

light/trie.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func (t *LightTrie) do(ctx context.Context, fallbackKey []byte, fn func() error)
7979
func (t *LightTrie) Get(ctx context.Context, key []byte) (res []byte, err error) {
8080
err = t.do(ctx, key, func() (err error) {
8181
if t.trie == nil {
82-
t.trie, err = trie.NewSecure(t.originalRoot, t.db)
82+
t.trie, err = trie.NewSecure(t.originalRoot, t.db, 0)
8383
}
8484
if err == nil {
8585
res, err = t.trie.TryGet(key)
@@ -98,7 +98,7 @@ func (t *LightTrie) Get(ctx context.Context, key []byte) (res []byte, err error)
9898
func (t *LightTrie) Update(ctx context.Context, key, value []byte) (err error) {
9999
err = t.do(ctx, key, func() (err error) {
100100
if t.trie == nil {
101-
t.trie, err = trie.NewSecure(t.originalRoot, t.db)
101+
t.trie, err = trie.NewSecure(t.originalRoot, t.db, 0)
102102
}
103103
if err == nil {
104104
err = t.trie.TryUpdate(key, value)
@@ -112,7 +112,7 @@ func (t *LightTrie) Update(ctx context.Context, key, value []byte) (err error) {
112112
func (t *LightTrie) Delete(ctx context.Context, key []byte) (err error) {
113113
err = t.do(ctx, key, func() (err error) {
114114
if t.trie == nil {
115-
t.trie, err = trie.NewSecure(t.originalRoot, t.db)
115+
t.trie, err = trie.NewSecure(t.originalRoot, t.db, 0)
116116
}
117117
if err == nil {
118118
err = t.trie.TryDelete(key)

trie/hasher.go

+48-30
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ import (
2727
)
2828

2929
type hasher struct {
30-
tmp *bytes.Buffer
31-
sha hash.Hash
30+
tmp *bytes.Buffer
31+
sha hash.Hash
32+
cachegen, cachelimit uint16
3233
}
3334

3435
// hashers live in a global pool.
@@ -38,8 +39,10 @@ var hasherPool = sync.Pool{
3839
},
3940
}
4041

41-
func newHasher() *hasher {
42-
return hasherPool.Get().(*hasher)
42+
func newHasher(cachegen, cachelimit uint16) *hasher {
43+
h := hasherPool.Get().(*hasher)
44+
h.cachegen, h.cachelimit = cachegen, cachelimit
45+
return h
4346
}
4447

4548
func returnHasherToPool(h *hasher) {
@@ -50,8 +53,18 @@ func returnHasherToPool(h *hasher) {
5053
// original node initialzied with the computed hash to replace the original one.
5154
func (h *hasher) hash(n node, db DatabaseWriter, force bool) (node, node, error) {
5255
// If we're not storing the node, just hashing, use avaialble cached data
53-
if hash, dirty := n.cache(); hash != nil && (db == nil || !dirty) {
54-
return hash, n, nil
56+
if hash, dirty := n.cache(); hash != nil {
57+
if db == nil {
58+
return hash, n, nil
59+
}
60+
if n.canUnload(h.cachegen, h.cachelimit) {
61+
// Evict the node from cache. All of its subnodes will have a lower or equal
62+
// cache generation number.
63+
return hash, hash, nil
64+
}
65+
if !dirty {
66+
return hash, n, nil
67+
}
5568
}
5669
// Trie not processed yet or needs storage, walk the children
5770
collapsed, cached, err := h.hashChildren(n, db)
@@ -62,19 +75,21 @@ func (h *hasher) hash(n node, db DatabaseWriter, force bool) (node, node, error)
6275
if err != nil {
6376
return hashNode{}, n, err
6477
}
65-
// Cache the hash and RLP blob of the ndoe for later reuse
78+
// Cache the hash of the ndoe for later reuse.
6679
if hash, ok := hashed.(hashNode); ok && !force {
6780
switch cached := cached.(type) {
68-
case shortNode:
69-
cached.hash = hash
81+
case *shortNode:
82+
cached = cached.copy()
83+
cached.flags.hash = hash
7084
if db != nil {
71-
cached.dirty = false
85+
cached.flags.dirty = false
7286
}
7387
return hashed, cached, nil
74-
case fullNode:
75-
cached.hash = hash
88+
case *fullNode:
89+
cached = cached.copy()
90+
cached.flags.hash = hash
7691
if db != nil {
77-
cached.dirty = false
92+
cached.flags.dirty = false
7893
}
7994
return hashed, cached, nil
8095
}
@@ -89,40 +104,42 @@ func (h *hasher) hashChildren(original node, db DatabaseWriter) (node, node, err
89104
var err error
90105

91106
switch n := original.(type) {
92-
case shortNode:
107+
case *shortNode:
93108
// Hash the short node's child, caching the newly hashed subtree
94-
cached := n
95-
cached.Key = common.CopyBytes(cached.Key)
109+
collapsed, cached := n.copy(), n.copy()
110+
collapsed.Key = compactEncode(n.Key)
111+
cached.Key = common.CopyBytes(n.Key)
96112

97-
n.Key = compactEncode(n.Key)
98113
if _, ok := n.Val.(valueNode); !ok {
99-
if n.Val, cached.Val, err = h.hash(n.Val, db, false); err != nil {
100-
return n, original, err
114+
collapsed.Val, cached.Val, err = h.hash(n.Val, db, false)
115+
if err != nil {
116+
return original, original, err
101117
}
102118
}
103-
if n.Val == nil {
104-
n.Val = valueNode(nil) // Ensure that nil children are encoded as empty strings.
119+
if collapsed.Val == nil {
120+
collapsed.Val = valueNode(nil) // Ensure that nil children are encoded as empty strings.
105121
}
106-
return n, cached, nil
122+
return collapsed, cached, nil
107123

108-
case fullNode:
124+
case *fullNode:
109125
// Hash the full node's children, caching the newly hashed subtrees
110-
cached := fullNode{dirty: n.dirty}
126+
collapsed, cached := n.copy(), n.copy()
111127

112128
for i := 0; i < 16; i++ {
113129
if n.Children[i] != nil {
114-
if n.Children[i], cached.Children[i], err = h.hash(n.Children[i], db, false); err != nil {
115-
return n, original, err
130+
collapsed.Children[i], cached.Children[i], err = h.hash(n.Children[i], db, false)
131+
if err != nil {
132+
return original, original, err
116133
}
117134
} else {
118-
n.Children[i] = valueNode(nil) // Ensure that nil children are encoded as empty strings.
135+
collapsed.Children[i] = valueNode(nil) // Ensure that nil children are encoded as empty strings.
119136
}
120137
}
121138
cached.Children[16] = n.Children[16]
122-
if n.Children[16] == nil {
123-
n.Children[16] = valueNode(nil)
139+
if collapsed.Children[16] == nil {
140+
collapsed.Children[16] = valueNode(nil)
124141
}
125-
return n, cached, nil
142+
return collapsed, cached, nil
126143

127144
default:
128145
// Value and hash nodes don't have children so they're left as were
@@ -140,6 +157,7 @@ func (h *hasher) store(n node, db DatabaseWriter, force bool) (node, error) {
140157
if err := rlp.Encode(h.tmp, n); err != nil {
141158
panic("encode error: " + err.Error())
142159
}
160+
143161
if h.tmp.Len() < 32 && !force {
144162
return n, nil // Nodes smaller than 32 bytes are stored inside their parent
145163
}

trie/iterator.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ func (it *Iterator) makeKey() []byte {
5858
key := it.keyBuf[:0]
5959
for _, se := range it.nodeIt.stack {
6060
switch node := se.node.(type) {
61-
case fullNode:
61+
case *fullNode:
6262
if se.child <= 16 {
6363
key = append(key, byte(se.child))
6464
}
65-
case shortNode:
65+
case *shortNode:
6666
if hasTerm(node.Key) {
6767
key = append(key, node.Key[:len(node.Key)-1]...)
6868
} else {
@@ -150,30 +150,30 @@ func (it *NodeIterator) step() error {
150150
if (ancestor == common.Hash{}) {
151151
ancestor = parent.parent
152152
}
153-
if node, ok := parent.node.(fullNode); ok {
153+
if node, ok := parent.node.(*fullNode); ok {
154154
// Full node, traverse all children, then the node itself
155155
if parent.child >= len(node.Children) {
156156
break
157157
}
158158
for parent.child++; parent.child < len(node.Children); parent.child++ {
159159
if current := node.Children[parent.child]; current != nil {
160160
it.stack = append(it.stack, &nodeIteratorState{
161-
hash: common.BytesToHash(node.hash),
161+
hash: common.BytesToHash(node.flags.hash),
162162
node: current,
163163
parent: ancestor,
164164
child: -1,
165165
})
166166
break
167167
}
168168
}
169-
} else if node, ok := parent.node.(shortNode); ok {
169+
} else if node, ok := parent.node.(*shortNode); ok {
170170
// Short node, traverse the pointer singleton child, then the node itself
171171
if parent.child >= 0 {
172172
break
173173
}
174174
parent.child++
175175
it.stack = append(it.stack, &nodeIteratorState{
176-
hash: common.BytesToHash(node.hash),
176+
hash: common.BytesToHash(node.flags.hash),
177177
node: node.Val,
178178
parent: ancestor,
179179
child: -1,

0 commit comments

Comments
 (0)