From a61024ed38a9fa7f122392aab90378a499b332fb Mon Sep 17 00:00:00 2001 From: testinginprod Date: Fri, 8 Nov 2024 09:08:08 +0100 Subject: [PATCH 1/3] checkpoint --- server/v2/stf/branch/bench_test.go | 43 +++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/server/v2/stf/branch/bench_test.go b/server/v2/stf/branch/bench_test.go index db46846c1677..d1aa56a2d5ab 100644 --- a/server/v2/stf/branch/bench_test.go +++ b/server/v2/stf/branch/bench_test.go @@ -1,6 +1,7 @@ package branch import ( + "encoding/binary" "fmt" "testing" @@ -14,29 +15,61 @@ var ( ) func Benchmark_CacheStack_Set(b *testing.B) { + var sink any for _, stackSize := range stackSizes { b.Run(fmt.Sprintf("StackSize%d", stackSize), func(b *testing.B) { bs := makeBranchStack(b, stackSize) b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { - _ = bs.Set([]byte{0}, []byte{0}) + sink = bs.Set([]byte{0}, []byte{0}) } }) } + if sink != nil { + b.Fatal("prevent compiler optimization") + } } func Benchmark_Get(b *testing.B) { + var sink any for _, stackSize := range stackSizes { b.Run(fmt.Sprintf("StackSize%d", stackSize), func(b *testing.B) { bs := makeBranchStack(b, stackSize) b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { - _, _ = bs.Get([]byte{0}) + sink, _ = bs.Get([]byte{0}) + } + }) + } + if sink == nil { + b.Fatal("prevent compiler optimization") + } +} + +func Benchmark_GetSparse(b *testing.B) { + var sink any + for _, stackSize := range stackSizes { + b.Run(fmt.Sprintf("StackSize%d", stackSize), func(b *testing.B) { + bs := makeBranchStack(b, stackSize) + keys := func() [][]byte { + var keys [][]byte + for i := 0; i < b.N; i++ { + keys = append(keys, numToBytes(i)) + } + return keys + }() + b.ResetTimer() + b.ReportAllocs() + for _, key := range keys { + sink, _ = bs.Get(key) } }) } + if sink == nil { + b.Fatal("prevent compiler optimization") + } } func Benchmark_Iterate(b *testing.B) { @@ -71,7 +104,7 @@ func makeBranchStack(b *testing.B, stackSize int) Store[store.KVStore] { branch = NewStore[store.KVStore](branch) for j := 0; j < elemsInStack; j++ { // create unique keys by including the branch index. - key := []byte{byte(i), byte(j)} + key := append(numToBytes(i), numToBytes(j)...) value := []byte{byte(j)} err := branch.Set(key, value) if err != nil { @@ -81,3 +114,7 @@ func makeBranchStack(b *testing.B, stackSize int) Store[store.KVStore] { } return branch } + +func numToBytes[T ~int](n T) []byte { + return binary.BigEndian.AppendUint64(nil, uint64(n)) +} From 7fff23a5cb0cea5c6f4d7f251af4a78fcb9c1233 Mon Sep 17 00:00:00 2001 From: testinginprod Date: Fri, 8 Nov 2024 10:28:09 +0100 Subject: [PATCH 2/3] checkpoint --- server/v2/stf/branch/cache.go | 24 ++++++++++++++++++++++++ server/v2/stf/branch/store.go | 7 ++++++- 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 server/v2/stf/branch/cache.go diff --git a/server/v2/stf/branch/cache.go b/server/v2/stf/branch/cache.go new file mode 100644 index 000000000000..8f1f604aa4cd --- /dev/null +++ b/server/v2/stf/branch/cache.go @@ -0,0 +1,24 @@ +package branch + +type memCache struct { + items map[string][]byte +} + +func newMemCache() memCache { + return memCache{items: make(map[string][]byte)} +} + +func (m memCache) get(key []byte) ([]byte, bool) { + v, ok := m.items[unsafeString(key)] + return v, ok +} + +func (m memCache) set(key, value []byte) { + // we do not use unsafe because these are stored + // indefinitely in the cache + m.items[string(key)] = value +} + +func (m memCache) delete(key []byte) { + m.items[string(key)] = nil +} diff --git a/server/v2/stf/branch/store.go b/server/v2/stf/branch/store.go index 19e32e8392a3..fb7b485b4f24 100644 --- a/server/v2/stf/branch/store.go +++ b/server/v2/stf/branch/store.go @@ -12,6 +12,7 @@ var _ store.Writer = (*Store[store.Reader])(nil) type Store[T store.Reader] struct { changeSet changeSet // ordered changeset. parent T + cache memCache } // NewStore creates a new Store object @@ -19,13 +20,14 @@ func NewStore[T store.Reader](parent T) Store[T] { return Store[T]{ changeSet: newChangeSet(), parent: parent, + cache: newMemCache(), } } // Get implements types.KVStore. func (s Store[T]) Get(key []byte) (value []byte, err error) { // if found in memory cache, immediately return. - value, found := s.changeSet.get(key) + value, found := s.cache.get(key) if found { return } @@ -35,6 +37,7 @@ func (s Store[T]) Get(key []byte) (value []byte, err error) { if err != nil { return nil, err } + s.cache.set(key, value) return value, nil } @@ -44,6 +47,7 @@ func (s Store[T]) Set(key, value []byte) error { return errors.New("cannot set a nil value") } s.changeSet.set(key, value) + s.cache.set(key, value) // persist in cache. return nil } @@ -59,6 +63,7 @@ func (s Store[T]) Has(key []byte) (bool, error) { // Delete implements types.KVStore. func (s Store[T]) Delete(key []byte) error { s.changeSet.delete(key) + s.cache.delete(key) return nil } From 71245a155576767f886974f9aec6c4e658c1d9c4 Mon Sep 17 00:00:00 2001 From: testinginprod Date: Fri, 8 Nov 2024 15:51:38 +0100 Subject: [PATCH 3/3] add benches for old cacheKV --- server/v2/stf/branch/cache.go | 24 ------ server/v2/stf/branch/store.go | 10 +-- store/cachekv/branch_bench_test.go | 116 +++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 32 deletions(-) delete mode 100644 server/v2/stf/branch/cache.go create mode 100644 store/cachekv/branch_bench_test.go diff --git a/server/v2/stf/branch/cache.go b/server/v2/stf/branch/cache.go deleted file mode 100644 index 8f1f604aa4cd..000000000000 --- a/server/v2/stf/branch/cache.go +++ /dev/null @@ -1,24 +0,0 @@ -package branch - -type memCache struct { - items map[string][]byte -} - -func newMemCache() memCache { - return memCache{items: make(map[string][]byte)} -} - -func (m memCache) get(key []byte) ([]byte, bool) { - v, ok := m.items[unsafeString(key)] - return v, ok -} - -func (m memCache) set(key, value []byte) { - // we do not use unsafe because these are stored - // indefinitely in the cache - m.items[string(key)] = value -} - -func (m memCache) delete(key []byte) { - m.items[string(key)] = nil -} diff --git a/server/v2/stf/branch/store.go b/server/v2/stf/branch/store.go index fb7b485b4f24..16358d22f30f 100644 --- a/server/v2/stf/branch/store.go +++ b/server/v2/stf/branch/store.go @@ -12,7 +12,6 @@ var _ store.Writer = (*Store[store.Reader])(nil) type Store[T store.Reader] struct { changeSet changeSet // ordered changeset. parent T - cache memCache } // NewStore creates a new Store object @@ -20,24 +19,21 @@ func NewStore[T store.Reader](parent T) Store[T] { return Store[T]{ changeSet: newChangeSet(), parent: parent, - cache: newMemCache(), } } // Get implements types.KVStore. func (s Store[T]) Get(key []byte) (value []byte, err error) { // if found in memory cache, immediately return. - value, found := s.cache.get(key) + value, found := s.changeSet.get(key) if found { return } - // after we get it from parent store, we cache it. - // if it is not found in parent store, we still cache it as nil. + // if not found in the changeset, then check the parent. value, err = s.parent.Get(key) if err != nil { return nil, err } - s.cache.set(key, value) return value, nil } @@ -47,7 +43,6 @@ func (s Store[T]) Set(key, value []byte) error { return errors.New("cannot set a nil value") } s.changeSet.set(key, value) - s.cache.set(key, value) // persist in cache. return nil } @@ -63,7 +58,6 @@ func (s Store[T]) Has(key []byte) (bool, error) { // Delete implements types.KVStore. func (s Store[T]) Delete(key []byte) error { s.changeSet.delete(key) - s.cache.delete(key) return nil } diff --git a/store/cachekv/branch_bench_test.go b/store/cachekv/branch_bench_test.go new file mode 100644 index 000000000000..cb1064e7c5c5 --- /dev/null +++ b/store/cachekv/branch_bench_test.go @@ -0,0 +1,116 @@ +package cachekv_test + +import ( + "encoding/binary" + "fmt" + "testing" + + coretesting "cosmossdk.io/core/testing" + "cosmossdk.io/store/cachekv" + "cosmossdk.io/store/dbadapter" +) + +var ( + stackSizes = []int{1, 10, 100} + elemsInStack = 10 +) + +func Benchmark_CacheStack_Set(b *testing.B) { + for _, stackSize := range stackSizes { + b.Run(fmt.Sprintf("StackSize%d", stackSize), func(b *testing.B) { + bs := makeBranchStack(b, stackSize) + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + bs.Set([]byte{0}, []byte{0}) + } + }) + } +} + +// Gets the same key from the branch store. +func Benchmark_Get(b *testing.B) { + var sink any + for _, stackSize := range stackSizes { + b.Run(fmt.Sprintf("StackSize%d", stackSize), func(b *testing.B) { + bs := makeBranchStack(b, stackSize) + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + sink = bs.Get([]byte{0}) + } + }) + } + if sink == nil { + b.Fatal("prevent compiler optimization") + } +} + +// Gets always different keys. +func Benchmark_GetSparse(b *testing.B) { + var sink any + for _, stackSize := range stackSizes { + b.Run(fmt.Sprintf("StackSize%d", stackSize), func(b *testing.B) { + bs := makeBranchStack(b, stackSize) + keys := func() [][]byte { + var keys [][]byte + for i := 0; i < b.N; i++ { + keys = append(keys, numToBytes(i)) + } + return keys + }() + b.ResetTimer() + b.ReportAllocs() + for _, key := range keys { + sink = bs.Get(key) + } + }) + } + if sink == nil { + b.Fatal("prevent compiler optimization") + } +} + +func Benchmark_Iterate(b *testing.B) { + var keySink, valueSink any + + for _, stackSize := range stackSizes { + b.Run(fmt.Sprintf("StackSize%d", stackSize), func(b *testing.B) { + bs := makeBranchStack(b, stackSize) + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + iter := bs.Iterator(nil, nil) + for iter.Valid() { + keySink = iter.Key() + valueSink = iter.Value() + iter.Next() + } + _ = iter.Close() + } + }) + } + + _ = keySink + _ = valueSink +} + +// makeBranchStack creates a branch stack of the given size and initializes it with unique key-value pairs. +func makeBranchStack(_ *testing.B, stackSize int) *cachekv.Store { + parent := dbadapter.Store{DB: coretesting.NewMemDB()} + branch := cachekv.NewStore(parent) + for i := 1; i < stackSize; i++ { + branch = cachekv.NewStore(branch) + for j := 0; j < elemsInStack; j++ { + // create unique keys by including the branch index. + key := append(numToBytes(i), numToBytes(j)...) + value := []byte{byte(j)} + branch.Set(key, value) + } + } + return branch +} + +func numToBytes[T ~int](n T) []byte { + return binary.BigEndian.AppendUint64(nil, uint64(n)) +}