diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go index 809f38b12..60a95990d 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_test.go @@ -2,6 +2,7 @@ package core import ( "errors" + "fmt" "math" "math/big" "testing" @@ -336,32 +337,98 @@ func TestStorageDelete(t *testing.T) { } func BenchmarkStorageFind(b *testing.B) { - v, contractState, context, chain := createVMAndContractState(b) - require.NoError(b, chain.contracts.Management.PutContractState(chain.dao, contractState)) + for count := 10; count <= 10000; count *= 10 { + b.Run(fmt.Sprintf("%dElements", count), func(b *testing.B) { + v, contractState, context, chain := createVMAndContractState(b) + require.NoError(b, chain.contracts.Management.PutContractState(chain.dao, contractState)) - const count = 100 + items := make(map[string]state.StorageItem) + for i := 0; i < count; i++ { + items["abc"+random.String(10)] = random.Bytes(10) + } + for k, v := range items { + require.NoError(b, context.DAO.PutStorageItem(contractState.ID, []byte(k), v)) + require.NoError(b, context.DAO.PutStorageItem(contractState.ID+1, []byte(k), v)) + } + changes, err := context.DAO.Persist() + require.NoError(b, err) + require.NotEqual(b, 0, changes) - items := make(map[string]state.StorageItem) - for i := 0; i < count; i++ { - items["abc"+random.String(10)] = random.Bytes(10) - } - for k, v := range items { - require.NoError(b, context.DAO.PutStorageItem(contractState.ID, []byte(k), v)) - require.NoError(b, context.DAO.PutStorageItem(contractState.ID+1, []byte(k), v)) + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + b.StopTimer() + v.Estack().PushVal(istorage.FindDefault) + v.Estack().PushVal("abc") + v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ID: contractState.ID})) + b.StartTimer() + err := storageFind(context) + if err != nil { + b.FailNow() + } + } + }) } +} - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - b.StopTimer() - v.Estack().PushVal(istorage.FindDefault) - v.Estack().PushVal("abc") - v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ID: contractState.ID})) - b.StartTimer() - err := storageFind(context) - if err != nil { - b.FailNow() +func BenchmarkStorageFindIteratorNext(b *testing.B) { + for count := 10; count <= 10000; count *= 10 { + cases := map[string]int{ + "Pick1": 1, + "PickHalf": count / 2, + "PickAll": count, } + b.Run(fmt.Sprintf("%dElements", count), func(b *testing.B) { + for name, last := range cases { + b.Run(name, func(b *testing.B) { + v, contractState, context, chain := createVMAndContractState(b) + require.NoError(b, chain.contracts.Management.PutContractState(chain.dao, contractState)) + + items := make(map[string]state.StorageItem) + for i := 0; i < count; i++ { + items["abc"+random.String(10)] = random.Bytes(10) + } + for k, v := range items { + require.NoError(b, context.DAO.PutStorageItem(contractState.ID, []byte(k), v)) + require.NoError(b, context.DAO.PutStorageItem(contractState.ID+1, []byte(k), v)) + } + changes, err := context.DAO.Persist() + require.NoError(b, err) + require.NotEqual(b, 0, changes) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + b.StopTimer() + v.Estack().PushVal(istorage.FindDefault) + v.Estack().PushVal("abc") + v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ID: contractState.ID})) + b.StartTimer() + err := storageFind(context) + b.StopTimer() + if err != nil { + b.FailNow() + } + res := context.VM.Estack().Pop().Item() + for i := 0; i < last; i++ { + context.VM.Estack().PushVal(res) + b.StartTimer() + require.NoError(b, iterator.Next(context)) + b.StopTimer() + require.True(b, context.VM.Estack().Pop().Bool()) + } + + context.VM.Estack().PushVal(res) + require.NoError(b, iterator.Next(context)) + actual := context.VM.Estack().Pop().Bool() + if last == count { + require.False(b, actual) + } else { + require.True(b, actual) + } + } + }) + } + }) } } diff --git a/pkg/core/storage/badgerdb_store_test.go b/pkg/core/storage/badgerdb_store_test.go index e00fa4a15..78a8fb957 100644 --- a/pkg/core/storage/badgerdb_store_test.go +++ b/pkg/core/storage/badgerdb_store_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/require" ) -func newBadgerDBForTesting(t *testing.T) Store { +func newBadgerDBForTesting(t testing.TB) Store { bdbDir := t.TempDir() dbConfig := DBConfiguration{ Type: "badgerdb", diff --git a/pkg/core/storage/boltdb_store_test.go b/pkg/core/storage/boltdb_store_test.go index 9f8fd4caf..cd1a84e39 100644 --- a/pkg/core/storage/boltdb_store_test.go +++ b/pkg/core/storage/boltdb_store_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/require" ) -func newBoltStoreForTesting(t *testing.T) Store { +func newBoltStoreForTesting(t testing.TB) Store { d := t.TempDir() testFileName := path.Join(d, "test_bolt_db") boltDBStore, err := NewBoltDBStore(BoltDBOptions{FilePath: testFileName}) diff --git a/pkg/core/storage/leveldb_store_test.go b/pkg/core/storage/leveldb_store_test.go index 5d8672e7a..24f21e76b 100644 --- a/pkg/core/storage/leveldb_store_test.go +++ b/pkg/core/storage/leveldb_store_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/require" ) -func newLevelDBForTesting(t *testing.T) Store { +func newLevelDBForTesting(t testing.TB) Store { ldbDir := t.TempDir() dbConfig := DBConfiguration{ Type: "leveldb", diff --git a/pkg/core/storage/memcached_store_test.go b/pkg/core/storage/memcached_store_test.go index 83d8cc623..5373bd3c1 100644 --- a/pkg/core/storage/memcached_store_test.go +++ b/pkg/core/storage/memcached_store_test.go @@ -1,8 +1,10 @@ package storage import ( + "fmt" "testing" + "github.com/nspcc-dev/neo-go/internal/random" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -174,7 +176,82 @@ func TestCachedSeek(t *testing.T) { } } -func newMemCachedStoreForTesting(t *testing.T) Store { +func benchmarkCachedSeek(t *testing.B, ps Store, psElementsCount, tsElementsCount int) { + var ( + searchPrefix = []byte{1} + badPrefix = []byte{2} + lowerPrefixGood = append(searchPrefix, 1) + lowerPrefixBad = append(badPrefix, 1) + deletedPrefixGood = append(searchPrefix, 2) + deletedPrefixBad = append(badPrefix, 2) + updatedPrefixGood = append(searchPrefix, 3) + updatedPrefixBad = append(badPrefix, 3) + + ts = NewMemCachedStore(ps) + ) + for i := 0; i < psElementsCount; i++ { + // lower KVs with matching prefix that should be found + require.NoError(t, ps.Put(append(lowerPrefixGood, random.Bytes(10)...), []byte("value"))) + // lower KVs with non-matching prefix that shouldn't be found + require.NoError(t, ps.Put(append(lowerPrefixBad, random.Bytes(10)...), []byte("value"))) + + // deleted KVs with matching prefix that shouldn't be found + key := append(deletedPrefixGood, random.Bytes(10)...) + require.NoError(t, ps.Put(key, []byte("deleted"))) + if i < tsElementsCount { + require.NoError(t, ts.Delete(key)) + } + // deleted KVs with non-matching prefix that shouldn't be found + key = append(deletedPrefixBad, random.Bytes(10)...) + require.NoError(t, ps.Put(key, []byte("deleted"))) + if i < tsElementsCount { + require.NoError(t, ts.Delete(key)) + } + + // updated KVs with matching prefix that should be found + key = append(updatedPrefixGood, random.Bytes(10)...) + require.NoError(t, ps.Put(key, []byte("stub"))) + if i < tsElementsCount { + require.NoError(t, ts.Put(key, []byte("updated"))) + } + // updated KVs with non-matching prefix that shouldn't be found + key = append(updatedPrefixBad, random.Bytes(10)...) + require.NoError(t, ps.Put(key, []byte("stub"))) + if i < tsElementsCount { + require.NoError(t, ts.Put(key, []byte("updated"))) + } + } + + t.ReportAllocs() + t.ResetTimer() + for n := 0; n < t.N; n++ { + ts.Seek(searchPrefix, func(k, v []byte) {}) + } + t.StopTimer() +} + +func BenchmarkCachedSeek(t *testing.B) { + var stores = map[string]func(testing.TB) Store{ + "MemPS": func(t testing.TB) Store { + return NewMemoryStore() + }, + "BoltPS": newBoltStoreForTesting, + "LevelPS": newLevelDBForTesting, + } + for psName, newPS := range stores { + for psCount := 100; psCount <= 10000; psCount *= 10 { + for tsCount := 10; tsCount <= psCount; tsCount *= 10 { + t.Run(fmt.Sprintf("%s_%dTSItems_%dPSItems", psName, tsCount, psCount), func(t *testing.B) { + ps := newPS(t) + benchmarkCachedSeek(t, ps, psCount, tsCount) + ps.Close() + }) + } + } + } +} + +func newMemCachedStoreForTesting(t testing.TB) Store { return NewMemCachedStore(NewMemoryStore()) } diff --git a/pkg/core/storage/memory_store_test.go b/pkg/core/storage/memory_store_test.go index 259dbae68..6bb7d7526 100644 --- a/pkg/core/storage/memory_store_test.go +++ b/pkg/core/storage/memory_store_test.go @@ -1,9 +1,35 @@ package storage import ( + "fmt" "testing" + + "github.com/nspcc-dev/neo-go/internal/random" + "github.com/stretchr/testify/require" ) -func newMemoryStoreForTesting(t *testing.T) Store { +func newMemoryStoreForTesting(t testing.TB) Store { return NewMemoryStore() } + +func BenchmarkMemorySeek(t *testing.B) { + for count := 10; count <= 10000; count *= 10 { + t.Run(fmt.Sprintf("%dElements", count), func(t *testing.B) { + ms := NewMemoryStore() + var ( + searchPrefix = []byte{1} + badPrefix = []byte{2} + ) + for i := 0; i < count; i++ { + require.NoError(t, ms.Put(append(searchPrefix, random.Bytes(10)...), random.Bytes(10))) + require.NoError(t, ms.Put(append(badPrefix, random.Bytes(10)...), random.Bytes(10))) + } + + t.ReportAllocs() + t.ResetTimer() + for n := 0; n < t.N; n++ { + ms.Seek(searchPrefix, func(k, v []byte) {}) + } + }) + } +} diff --git a/pkg/core/storage/redis_store_test.go b/pkg/core/storage/redis_store_test.go index d5613303b..f18aca684 100644 --- a/pkg/core/storage/redis_store_test.go +++ b/pkg/core/storage/redis_store_test.go @@ -12,7 +12,7 @@ type mockedRedisStore struct { mini *miniredis.Miniredis } -func prepareRedisMock(t *testing.T) (*miniredis.Miniredis, *RedisStore) { +func prepareRedisMock(t testing.TB) (*miniredis.Miniredis, *RedisStore) { miniRedis, err := miniredis.Run() require.Nil(t, err, "MiniRedis mock creation error") @@ -37,7 +37,7 @@ func (mrs *mockedRedisStore) Close() error { return err } -func newRedisStoreForTesting(t *testing.T) Store { +func newRedisStoreForTesting(t testing.TB) Store { mock, rs := prepareRedisMock(t) mrs := &mockedRedisStore{RedisStore: *rs, mini: mock} return mrs diff --git a/pkg/core/storage/storeandbatch_test.go b/pkg/core/storage/storeandbatch_test.go index 022d83adb..70a3787ea 100644 --- a/pkg/core/storage/storeandbatch_test.go +++ b/pkg/core/storage/storeandbatch_test.go @@ -19,7 +19,7 @@ type kvSeen struct { type dbSetup struct { name string - create func(*testing.T) Store + create func(testing.TB) Store } type dbTestFunction func(*testing.T, Store)