Skip to content

Commit acbbfb9

Browse files
feat(perf): Create a pooled version of the cachekv store
This means that hot code that provisions caches, such as runTx, can re-use already allocated memory-space for their cache, without having to suffer the hit of a new allocation.
1 parent 018f9e1 commit acbbfb9

File tree

5 files changed

+124
-22
lines changed

5 files changed

+124
-22
lines changed

store/cachekv/internal/btree.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ func NewBTree() BTree {
3636
}
3737
}
3838

39+
func (bt BTree) Clear() {
40+
bt.tree.Clear()
41+
}
42+
3943
func (bt BTree) Set(key, value []byte) {
4044
bt.tree.Set(newItem(key, value))
4145
}

store/cachekv/store.go

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ type Store struct {
3232
parent types.KVStore
3333
}
3434

35+
type PooledStore struct {
36+
Store
37+
}
38+
3539
var _ types.CacheKVStore = (*Store)(nil)
3640

3741
// NewStore creates a new Store object
@@ -44,6 +48,31 @@ func NewStore(parent types.KVStore) *Store {
4448
}
4549
}
4650

51+
var storePool = sync.Pool{
52+
New: func() any {
53+
return &PooledStore{
54+
Store: Store{
55+
cache: make(map[string]*cValue),
56+
unsortedCache: make(map[string]struct{}),
57+
sortedCache: internal.NewBTree(),
58+
},
59+
}
60+
},
61+
}
62+
63+
func (store *PooledStore) Release() {
64+
store.resetCaches()
65+
store.parent = nil
66+
store.mtx = sync.Mutex{}
67+
storePool.Put(store)
68+
}
69+
70+
func NewPooledStore(parent types.KVStore) *PooledStore {
71+
store := storePool.Get().(*PooledStore)
72+
store.parent = parent
73+
return store
74+
}
75+
4776
// GetStoreType implements Store.
4877
func (store *Store) GetStoreType() types.StoreType {
4978
return store.parent.GetStoreType()
@@ -112,7 +141,7 @@ func (store *Store) resetCaches() {
112141
delete(store.unsortedCache, key)
113142
}
114143
}
115-
store.sortedCache = internal.NewBTree()
144+
store.sortedCache.Clear()
116145
}
117146

118147
// Implements Cachetypes.KVStore.
@@ -121,7 +150,7 @@ func (store *Store) Write() {
121150
defer store.mtx.Unlock()
122151

123152
if len(store.cache) == 0 && len(store.unsortedCache) == 0 {
124-
store.sortedCache = internal.NewBTree()
153+
store.sortedCache.Clear()
125154
return
126155
}
127156

store/cachemulti/store.go

Lines changed: 81 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"io"
66
"maps"
7+
"sync"
78

89
dbm "github.com/cosmos/cosmos-db"
910

@@ -33,16 +34,22 @@ type Store struct {
3334
traceContext types.TraceContext
3435
}
3536

36-
var _ types.CacheMultiStore = Store{}
37+
type PooledStore struct {
38+
Store
39+
}
40+
41+
var _ types.CacheMultiStore = &Store{}
42+
43+
var _ types.PooledCacheMultiStore = &PooledStore{}
3744

3845
// NewFromKVStore creates a new Store object from a mapping of store keys to
3946
// CacheWrapper objects and a KVStore as the database. Each CacheWrapper store
4047
// is a branched store.
4148
func NewFromKVStore(
4249
store types.KVStore, stores map[types.StoreKey]types.CacheWrapper,
4350
keys map[string]types.StoreKey, traceWriter io.Writer, traceContext types.TraceContext,
44-
) Store {
45-
cms := Store{
51+
) *Store {
52+
cms := &Store{
4653
db: cachekv.NewStore(store),
4754
stores: make(map[types.StoreKey]types.CacheWrap, len(stores)),
4855
keys: keys,
@@ -69,11 +76,64 @@ func NewFromKVStore(
6976
func NewStore(
7077
db dbm.DB, stores map[types.StoreKey]types.CacheWrapper, keys map[string]types.StoreKey,
7178
traceWriter io.Writer, traceContext types.TraceContext,
72-
) Store {
79+
) *Store {
7380
return NewFromKVStore(dbadapter.Store{DB: db}, stores, keys, traceWriter, traceContext)
7481
}
7582

76-
func newCacheMultiStoreFromCMS(cms Store) Store {
83+
var storePool = sync.Pool{
84+
New: func() any {
85+
return &PooledStore{
86+
Store: Store{
87+
stores: make(map[types.StoreKey]types.CacheWrap),
88+
keys: make(map[string]types.StoreKey),
89+
},
90+
}
91+
},
92+
}
93+
94+
func newFromKVStorePooled(
95+
store types.KVStore, stores map[types.StoreKey]types.CacheWrap,
96+
traceWriter io.Writer, traceContext types.TraceContext,
97+
) *PooledStore {
98+
cms := storePool.Get().(*PooledStore)
99+
cms.traceWriter = traceWriter
100+
cms.traceContext = traceContext
101+
for key, store := range stores {
102+
var cwStore types.CacheWrapper = store
103+
if cms.TracingEnabled() {
104+
tctx := cms.traceContext.Clone().Merge(types.TraceContext{
105+
storeNameCtxKey: key.Name(),
106+
})
107+
108+
cwStore = tracekv.NewStore(store.(types.KVStore), cms.traceWriter, tctx)
109+
}
110+
cms.stores[key] = cachekv.NewPooledStore(cwStore.(types.KVStore))
111+
}
112+
cms.db = cachekv.NewPooledStore(store)
113+
return cms
114+
}
115+
116+
func (cms *PooledStore) Release() {
117+
// clear the stores map
118+
for k, v := range cms.stores {
119+
if pStore, ok := v.(*cachekv.PooledStore); ok {
120+
pStore.Release()
121+
}
122+
delete(cms.stores, k)
123+
}
124+
for k := range cms.keys {
125+
delete(cms.keys, k)
126+
}
127+
if pStoreDb, ok := cms.db.(*cachekv.PooledStore); ok {
128+
pStoreDb.Release()
129+
}
130+
cms.db = nil
131+
cms.traceContext = nil
132+
cms.traceWriter = nil
133+
storePool.Put(cms)
134+
}
135+
136+
func newCacheMultiStoreFromCMS(cms *Store) *Store {
77137
stores := make(map[types.StoreKey]types.CacheWrapper)
78138
for k, v := range cms.stores {
79139
stores[k] = v
@@ -84,7 +144,7 @@ func newCacheMultiStoreFromCMS(cms Store) Store {
84144

85145
// SetTracer sets the tracer for the MultiStore that the underlying
86146
// stores will utilize to trace operations. A MultiStore is returned.
87-
func (cms Store) SetTracer(w io.Writer) types.MultiStore {
147+
func (cms *Store) SetTracer(w io.Writer) types.MultiStore {
88148
cms.traceWriter = w
89149
return cms
90150
}
@@ -93,7 +153,7 @@ func (cms Store) SetTracer(w io.Writer) types.MultiStore {
93153
// the given context with the existing context by key. Any existing keys will
94154
// be overwritten. It is implied that the caller should update the context when
95155
// necessary between tracing operations. It returns a modified MultiStore.
96-
func (cms Store) SetTracingContext(tc types.TraceContext) types.MultiStore {
156+
func (cms *Store) SetTracingContext(tc types.TraceContext) types.MultiStore {
97157
if cms.traceContext != nil {
98158
maps.Copy(cms.traceContext, tc)
99159
} else {
@@ -104,54 +164,58 @@ func (cms Store) SetTracingContext(tc types.TraceContext) types.MultiStore {
104164
}
105165

106166
// TracingEnabled returns if tracing is enabled for the MultiStore.
107-
func (cms Store) TracingEnabled() bool {
167+
func (cms *Store) TracingEnabled() bool {
108168
return cms.traceWriter != nil
109169
}
110170

111171
// LatestVersion returns the branch version of the store
112-
func (cms Store) LatestVersion() int64 {
172+
func (cms *Store) LatestVersion() int64 {
113173
panic("cannot get latest version from branch cached multi-store")
114174
}
115175

116176
// GetStoreType returns the type of the store.
117-
func (cms Store) GetStoreType() types.StoreType {
177+
func (cms *Store) GetStoreType() types.StoreType {
118178
return types.StoreTypeMulti
119179
}
120180

121181
// Write calls Write on each underlying store.
122-
func (cms Store) Write() {
182+
func (cms *Store) Write() {
123183
cms.db.Write()
124184
for _, store := range cms.stores {
125185
store.Write()
126186
}
127187
}
128188

129189
// Implements CacheWrapper.
130-
func (cms Store) CacheWrap() types.CacheWrap {
190+
func (cms *Store) CacheWrap() types.CacheWrap {
131191
return cms.CacheMultiStore().(types.CacheWrap)
132192
}
133193

134194
// CacheWrapWithTrace implements the CacheWrapper interface.
135-
func (cms Store) CacheWrapWithTrace(_ io.Writer, _ types.TraceContext) types.CacheWrap {
195+
func (cms *Store) CacheWrapWithTrace(_ io.Writer, _ types.TraceContext) types.CacheWrap {
136196
return cms.CacheWrap()
137197
}
138198

139199
// Implements MultiStore.
140-
func (cms Store) CacheMultiStore() types.CacheMultiStore {
200+
func (cms *Store) CacheMultiStore() types.CacheMultiStore {
141201
return newCacheMultiStoreFromCMS(cms)
142202
}
143203

204+
func (cms *Store) CacheMultiStorePooled() types.PooledCacheMultiStore {
205+
return newFromKVStorePooled(cms.db, cms.stores, cms.traceWriter, cms.traceContext)
206+
}
207+
144208
// CacheMultiStoreWithVersion implements the MultiStore interface. It will panic
145209
// as an already cached multi-store cannot load previous versions.
146210
//
147211
// TODO: The store implementation can possibly be modified to support this as it
148212
// seems safe to load previous versions (heights).
149-
func (cms Store) CacheMultiStoreWithVersion(_ int64) (types.CacheMultiStore, error) {
213+
func (cms *Store) CacheMultiStoreWithVersion(_ int64) (types.CacheMultiStore, error) {
150214
panic("cannot branch cached multi-store with a version")
151215
}
152216

153217
// GetStore returns an underlying Store by key.
154-
func (cms Store) GetStore(key types.StoreKey) types.Store {
218+
func (cms *Store) GetStore(key types.StoreKey) types.Store {
155219
s := cms.stores[key]
156220
if key == nil || s == nil {
157221
panic(fmt.Sprintf("kv store with key %v has not been registered in stores", key))
@@ -160,7 +224,7 @@ func (cms Store) GetStore(key types.StoreKey) types.Store {
160224
}
161225

162226
// GetKVStore returns an underlying KVStore by key.
163-
func (cms Store) GetKVStore(key types.StoreKey) types.KVStore {
227+
func (cms *Store) GetKVStore(key types.StoreKey) types.KVStore {
164228
store := cms.stores[key]
165229
if key == nil || store == nil {
166230
panic(fmt.Sprintf("kv store with key %v has not been registered in stores", key))

store/rootmulti/store_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func TestCacheMultiStore(t *testing.T) {
6464
ms := newMultiStoreWithMounts(db, pruningtypes.NewPruningOptions(pruningtypes.PruningNothing))
6565

6666
cacheMulti := ms.CacheMultiStore()
67-
require.IsType(t, cachemulti.Store{}, cacheMulti)
67+
require.IsType(t, &cachemulti.Store{}, cacheMulti)
6868
}
6969

7070
func TestCacheMultiStoreWithVersion(t *testing.T) {
@@ -701,10 +701,10 @@ func TestCacheWraps(t *testing.T) {
701701
multi := newMultiStoreWithMounts(db, pruningtypes.NewPruningOptions(pruningtypes.PruningNothing))
702702

703703
cacheWrapper := multi.CacheWrap()
704-
require.IsType(t, cachemulti.Store{}, cacheWrapper)
704+
require.IsType(t, &cachemulti.Store{}, cacheWrapper)
705705

706706
cacheWrappedWithTrace := multi.CacheWrapWithTrace(nil, nil)
707-
require.IsType(t, cachemulti.Store{}, cacheWrappedWithTrace)
707+
require.IsType(t, &cachemulti.Store{}, cacheWrappedWithTrace)
708708
}
709709

710710
func TestTraceConcurrency(t *testing.T) {

store/types/store.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,11 @@ type CacheMultiStore interface {
152152
Write() // Writes operations to underlying KVStore
153153
}
154154

155+
type PooledCacheMultiStore interface {
156+
CacheMultiStore
157+
Release() // Releases the cache
158+
}
159+
155160
// CommitMultiStore is an interface for a MultiStore without cache capabilities.
156161
type CommitMultiStore interface {
157162
Committer

0 commit comments

Comments
 (0)