Skip to content

Commit dbd6ad5

Browse files
pdrobnjakclaude
andcommitted
perf: pool cachekv.Store to reduce allocation pressure
Replace per-tx cachekv.Store allocation with sync.Pool recycling for both standard and giga variants. Add CacheMultiStore.Release() and ReleaseDB() to return stores to pools at lifecycle boundaries (Cleanup, RevertToSnapshot, CleanupForTracer). Release replaced stores in SetKVStores/SetGigaKVStores and unused db store in OCC scheduler. Reset() replaces sync.Map fields with fresh instances (not Clear(), which is slower due to internal trie node walking and causes more allocations when repopulated). Targeting the #1 flat allocator from profiling: cachekv.NewStore at 9 GB / 157M objects over 30s at 8600 TPS. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b8fc7c9 commit dbd6ad5

File tree

8 files changed

+167
-18
lines changed

8 files changed

+167
-18
lines changed

giga/deps/store/cachekv.go

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,36 @@ type Store struct {
2222

2323
var _ types.CacheKVStore = (*Store)(nil)
2424

25+
var storePool = sync.Pool{
26+
New: func() any {
27+
return &Store{
28+
cache: &sync.Map{},
29+
deleted: &sync.Map{},
30+
}
31+
},
32+
}
33+
2534
// NewStore creates a new Store object
2635
func NewStore(parent types.KVStore, storeKey types.StoreKey, cacheSize int) *Store {
27-
return &Store{
28-
cache: &sync.Map{},
29-
deleted: &sync.Map{},
30-
parent: parent,
31-
storeKey: storeKey,
32-
cacheSize: cacheSize,
33-
}
36+
s := storePool.Get().(*Store)
37+
s.parent = parent
38+
s.storeKey = storeKey
39+
s.cacheSize = cacheSize
40+
return s
41+
}
42+
43+
// Reset clears all cached state, making the store ready for reuse.
44+
func (store *Store) Reset() {
45+
store.cache = &sync.Map{}
46+
store.deleted = &sync.Map{}
47+
store.parent = nil
48+
store.storeKey = nil
49+
}
50+
51+
// Release resets the store and returns it to the pool.
52+
func (store *Store) Release() {
53+
store.Reset()
54+
storePool.Put(store)
3455
}
3556

3657
func (store *Store) GetWorkingHash() ([]byte, error) {

giga/deps/tasks/scheduler.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,11 @@ func (s *scheduler) prepareTask(task *deliverTxTask) {
508508
return vs[k]
509509
})
510510

511+
// Release the db store since OCC scheduler doesn't use it
512+
if r, ok := ms.(interface{ ReleaseDB() }); ok {
513+
r.ReleaseDB()
514+
}
515+
511516
ctx = ctx.WithMultiStore(ms)
512517
}
513518

giga/deps/xevm/state/state.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,18 @@ func (s *DBImpl) RevertToSnapshot(rev int) {
116116
panic("invalid revision number")
117117
}
118118

119+
// Release current ctx's CMS (being abandoned)
120+
type releasable interface{ Release() }
121+
if r, ok := s.ctx.MultiStore().(releasable); ok {
122+
r.Release()
123+
}
124+
// Release abandoned snapshots (rev+1..end), but not rev (becomes new ctx)
125+
for i := len(s.snapshottedCtxs) - 1; i > rev; i-- {
126+
if r, ok := s.snapshottedCtxs[i].MultiStore().(releasable); ok {
127+
r.Release()
128+
}
129+
}
130+
119131
s.ctx = s.snapshottedCtxs[rev]
120132
s.snapshottedCtxs = s.snapshottedCtxs[:rev]
121133

giga/deps/xevm/state/statedb.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,29 @@ func (s *DBImpl) SetEVM(evm *vm.EVM) {}
8080
func (s *DBImpl) AddPreimage(_ common.Hash, _ []byte) {}
8181

8282
func (s *DBImpl) Cleanup() {
83+
s.releaseIntermediateStores()
8384
s.tempState = nil
8485
s.logger = nil
8586
s.snapshottedCtxs = nil
8687
}
8788

89+
// releaseIntermediateStores returns cachekv stores from intermediate CMS snapshots
90+
// back to their pools. Never releases snapshottedCtxs[0] — it belongs to the caller.
91+
func (s *DBImpl) releaseIntermediateStores() {
92+
type releasable interface{ Release() }
93+
if r, ok := s.ctx.MultiStore().(releasable); ok {
94+
r.Release()
95+
}
96+
for i := len(s.snapshottedCtxs) - 1; i > 0; i-- {
97+
if r, ok := s.snapshottedCtxs[i].MultiStore().(releasable); ok {
98+
r.Release()
99+
}
100+
}
101+
}
102+
88103
func (s *DBImpl) CleanupForTracer() {
89104
s.flushCtxs()
105+
s.releaseIntermediateStores()
90106
if len(s.snapshottedCtxs) > 0 {
91107
s.ctx = s.snapshottedCtxs[0]
92108
}

sei-cosmos/store/cachekv/store.go

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,39 @@ type Store struct {
2727

2828
var _ types.CacheKVStore = (*Store)(nil)
2929

30+
var storePool = sync.Pool{
31+
New: func() any {
32+
return &Store{
33+
cache: &sync.Map{},
34+
deleted: &sync.Map{},
35+
unsortedCache: &sync.Map{},
36+
}
37+
},
38+
}
39+
3040
// NewStore creates a new Store object
3141
func NewStore(parent types.KVStore, storeKey types.StoreKey, cacheSize int) *Store {
32-
return &Store{
33-
cache: &sync.Map{},
34-
deleted: &sync.Map{},
35-
unsortedCache: &sync.Map{},
36-
sortedCache: nil,
37-
parent: parent,
38-
storeKey: storeKey,
39-
cacheSize: cacheSize,
40-
}
42+
s := storePool.Get().(*Store)
43+
s.parent = parent
44+
s.storeKey = storeKey
45+
s.cacheSize = cacheSize
46+
return s
47+
}
48+
49+
// Reset clears all cached state, making the store ready for reuse.
50+
func (store *Store) Reset() {
51+
store.cache = &sync.Map{}
52+
store.deleted = &sync.Map{}
53+
store.unsortedCache = &sync.Map{}
54+
store.sortedCache = nil
55+
store.parent = nil
56+
store.storeKey = nil
57+
}
58+
59+
// Release resets the store and returns it to the pool.
60+
func (store *Store) Release() {
61+
store.Reset()
62+
storePool.Put(store)
4163
}
4264

4365
func (store *Store) GetWorkingHash() ([]byte, error) {

sei-cosmos/store/cachemulti/store.go

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,21 +281,66 @@ func (cms Store) StoreKeys() []types.StoreKey {
281281
return keys
282282
}
283283

284+
// Release returns all cachekv stores to their pools.
285+
func (cms Store) Release() {
286+
type releasable interface{ Release() }
287+
if r, ok := cms.db.(releasable); ok {
288+
r.Release()
289+
}
290+
for k, s := range cms.stores {
291+
if r, ok := s.(releasable); ok {
292+
r.Release()
293+
}
294+
delete(cms.stores, k)
295+
}
296+
for k, s := range cms.gigaStores {
297+
if r, ok := s.(releasable); ok {
298+
r.Release()
299+
}
300+
delete(cms.gigaStores, k)
301+
}
302+
for k := range cms.parents {
303+
delete(cms.parents, k)
304+
}
305+
}
306+
307+
// ReleaseDB releases the db cachekv store back to its pool.
308+
func (cms Store) ReleaseDB() {
309+
type releasable interface{ Release() }
310+
if r, ok := cms.db.(releasable); ok {
311+
r.Release()
312+
}
313+
}
314+
284315
// SetKVStores sets the underlying KVStores via a handler for each key
285316
func (cms Store) SetKVStores(handler func(sk types.StoreKey, s types.KVStore) types.CacheWrap) types.MultiStore {
286317
// Force-create any lazy stores
287318
for k := range cms.parents {
288319
cms.getOrCreateStore(k)
289320
}
321+
type releasable interface{ Release() }
290322
for k, s := range cms.stores {
291-
cms.stores[k] = handler(k, s.(types.KVStore))
323+
newStore := handler(k, s.(types.KVStore))
324+
if newStore != s {
325+
if r, ok := s.(releasable); ok {
326+
r.Release()
327+
}
328+
}
329+
cms.stores[k] = newStore
292330
}
293331
return cms
294332
}
295333

296334
func (cms Store) SetGigaKVStores(handler func(sk types.StoreKey, s types.KVStore) types.KVStore) types.MultiStore {
335+
type releasable interface{ Release() }
297336
for k, s := range cms.gigaStores {
298-
cms.gigaStores[k] = handler(k, s)
337+
newStore := handler(k, s)
338+
if newStore != s {
339+
if r, ok := s.(releasable); ok {
340+
r.Release()
341+
}
342+
}
343+
cms.gigaStores[k] = newStore
299344
}
300345
return cms
301346
}

x/evm/state/state.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,18 @@ func (s *DBImpl) RevertToSnapshot(rev int) {
121121
panic("invalid revision number")
122122
}
123123

124+
// Release current ctx's CMS (being abandoned)
125+
type releasable interface{ Release() }
126+
if r, ok := s.ctx.MultiStore().(releasable); ok {
127+
r.Release()
128+
}
129+
// Release abandoned snapshots (rev+1..end), but not rev (becomes new ctx)
130+
for i := len(s.snapshottedCtxs) - 1; i > rev; i-- {
131+
if r, ok := s.snapshottedCtxs[i].MultiStore().(releasable); ok {
132+
r.Release()
133+
}
134+
}
135+
124136
s.ctx = s.snapshottedCtxs[rev]
125137
s.snapshottedCtxs = s.snapshottedCtxs[:rev]
126138

x/evm/state/statedb.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,29 @@ func (s *DBImpl) SetEVM(evm *vm.EVM) {}
8080
func (s *DBImpl) AddPreimage(_ common.Hash, _ []byte) {}
8181

8282
func (s *DBImpl) Cleanup() {
83+
s.releaseIntermediateStores()
8384
s.tempState = nil
8485
s.logger = nil
8586
s.snapshottedCtxs = nil
8687
}
8788

89+
// releaseIntermediateStores returns cachekv stores from intermediate CMS snapshots
90+
// back to their pools. Never releases snapshottedCtxs[0] — it belongs to the caller.
91+
func (s *DBImpl) releaseIntermediateStores() {
92+
type releasable interface{ Release() }
93+
if r, ok := s.ctx.MultiStore().(releasable); ok {
94+
r.Release()
95+
}
96+
for i := len(s.snapshottedCtxs) - 1; i > 0; i-- {
97+
if r, ok := s.snapshottedCtxs[i].MultiStore().(releasable); ok {
98+
r.Release()
99+
}
100+
}
101+
}
102+
88103
func (s *DBImpl) CleanupForTracer() {
89104
s.flushCtxs()
105+
s.releaseIntermediateStores()
90106
if len(s.snapshottedCtxs) > 0 {
91107
s.ctx = s.snapshottedCtxs[0]
92108
}

0 commit comments

Comments
 (0)