diff --git a/CHANGELOG.md b/CHANGELOG.md index 0513560af35..3d78caf1cb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,10 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### 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. * (store) [\#10741](https://github.com/cosmos/cosmos-sdk/pull/10741) Significantly speedup iterator creation after delete heavy workloads. Significantly improves IBC migration times. * (types) [\#10076](https://github.com/cosmos/cosmos-sdk/pull/10076) Significantly speedup and lower allocations for `Coins.String()`. diff --git a/store/cachekv/store.go b/store/cachekv/store.go index fa9a601d477..3e9afff83bc 100644 --- a/store/cachekv/store.go +++ b/store/cachekv/store.go @@ -118,22 +118,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() }