diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fc9e797f..535c744cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,10 @@ Ref: https://keepachangelog.com/en/1.0.0/ * [\#10326](https://github.com/cosmos/cosmos-sdk/pull/10326) `x/authz` add query all grants by granter query. * [\#10348](https://github.com/cosmos/cosmos-sdk/pull/10348) Add `fee.{payer,granter}` and `tip` fields to StdSignDoc for signing tipped transactions. +### Improvements + +* [\#10486](https://github.com/cosmos/cosmos-sdk/pull/10486) store/cachekv's `Store.Write` conservatively looks up keys, but also uses the [map clearing idiom](https://bencher.orijtech.com/perfclinic/mapclearing/) to reduce the RAM usage, CPU time usage, and garbage collection pressure from clearing maps, instead of allocating new maps. + ### API Breaking Changes * (x/mint) [\#10441](https://github.com/cosmos/cosmos-sdk/pull/10441) The `NewAppModule` function now accepts an inflation calculation function as an argument. diff --git a/store/cachekv/store.go b/store/cachekv/store.go index 471c2387c..68fe7213d 100644 --- a/store/cachekv/store.go +++ b/store/cachekv/store.go @@ -114,22 +114,34 @@ func (store *Store) Write() { // TODO: Consider allowing usage of Batch, which would allow the write to // at least happen atomically. for _, key := range keys { - cacheValue := store.cache[key] - - switch { - case store.isDeleted(key): + if store.isDeleted(key) { + // We use []byte(key) instead of conv.UnsafeStrToBytes because we cannot + // be sure if the underlying store might do a save with the byteslice or + // not. Once we get confirmation that .Delete is guaranteed not to + // save the byteslice, then we can assume only a read-only copy is sufficient. store.parent.Delete([]byte(key)) - case cacheValue.value == nil: - // Skip, it already doesn't exist in parent. - default: + continue + } + + cacheValue := store.cache[key] + if cacheValue.value != nil { + // It already exists in the parent, hence delete it. store.parent.Set([]byte(key), cacheValue.value) } } - // Clear the cache - store.cache = make(map[string]*cValue) - store.deleted = make(map[string]struct{}) - store.unsortedCache = make(map[string]struct{}) + // Clear the cache using the map clearing idiom + // and not allocating fresh objects. + // Please see https://bencher.orijtech.com/perfclinic/mapclearing/ + for key := range store.cache { + delete(store.cache, key) + } + for key := range store.deleted { + delete(store.deleted, key) + } + for key := range store.unsortedCache { + delete(store.unsortedCache, key) + } store.sortedCache = dbm.NewMemDB() }