From 3ebd7e53ed114eb88bb3f10b477bd07043f05420 Mon Sep 17 00:00:00 2001 From: testinginprod <98415576+testinginprod@users.noreply.github.com> Date: Fri, 22 Nov 2024 13:15:02 +0100 Subject: [PATCH] feat(serverv2): add benchmarks of (old) cacheKV vs branch (#22497) --- server/v2/stf/branch/bench_test.go | 60 +++++++++++++-- server/v2/stf/branch/store.go | 3 +- store/cachekv/branch_bench_test.go | 120 +++++++++++++++++++++++++++++ 3 files changed, 173 insertions(+), 10 deletions(-) create mode 100644 store/cachekv/branch_bench_test.go diff --git a/server/v2/stf/branch/bench_test.go b/server/v2/stf/branch/bench_test.go index db46846c1677..f275e8d4352f 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" @@ -20,12 +21,17 @@ func Benchmark_CacheStack_Set(b *testing.B) { b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { - _ = bs.Set([]byte{0}, []byte{0}) + err := bs.Set([]byte{0}, []byte{0}) + if err != nil { + b.Fatal(err) + } } }) } } +var sink any + func Benchmark_Get(b *testing.B) { for _, stackSize := range stackSizes { b.Run(fmt.Sprintf("StackSize%d", stackSize), func(b *testing.B) { @@ -33,15 +39,47 @@ func Benchmark_Get(b *testing.B) { 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("benchmark did not run") + } + sink = nil } -func Benchmark_Iterate(b *testing.B) { - var keySink, valueSink any +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("benchmark did not run") + } + sink = nil +} + +var ( + keySink any + valueSink any +) +func Benchmark_Iterate(b *testing.B) { for _, stackSize := range stackSizes { b.Run(fmt.Sprintf("StackSize%d", stackSize), func(b *testing.B) { bs := makeBranchStack(b, stackSize) @@ -58,9 +96,11 @@ func Benchmark_Iterate(b *testing.B) { } }) } - - _ = keySink - _ = valueSink + if valueSink == nil || keySink == nil { + b.Fatal("benchmark did not run") + } + valueSink = nil + keySink = nil } // makeBranchStack creates a branch stack of the given size and initializes it with unique key-value pairs. @@ -71,7 +111,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 +121,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)) +} diff --git a/server/v2/stf/branch/store.go b/server/v2/stf/branch/store.go index 19e32e8392a3..16358d22f30f 100644 --- a/server/v2/stf/branch/store.go +++ b/server/v2/stf/branch/store.go @@ -29,8 +29,7 @@ func (s Store[T]) Get(key []byte) (value []byte, err error) { 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 diff --git a/store/cachekv/branch_bench_test.go b/store/cachekv/branch_bench_test.go new file mode 100644 index 000000000000..6cb393aa9b43 --- /dev/null +++ b/store/cachekv/branch_bench_test.go @@ -0,0 +1,120 @@ +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) { + 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") + } + sink = nil +} + +// Gets always different keys. +func Benchmark_GetSparse(b *testing.B) { + 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("Benchmark did not run") + } + sink = nil +} + +var keySink, valueSink any + +func Benchmark_Iterate(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++ { + iter := bs.Iterator(nil, nil) + for iter.Valid() { + keySink = iter.Key() + valueSink = iter.Value() + iter.Next() + } + _ = iter.Close() + } + }) + } + + if keySink == nil || valueSink == nil { + b.Fatal("Benchmark did not run") + } + keySink = nil + valueSink = nil +} + +// 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)) +}