core: add benchmarks for iterator.Next, MemCached.Seek, Mem.Seek

This commit is contained in:
Anna Shaleva 2021-10-05 15:13:19 +03:00
parent 8d8071f97e
commit d8210c0137
8 changed files with 199 additions and 29 deletions

View file

@ -2,6 +2,7 @@ package core
import ( import (
"errors" "errors"
"fmt"
"math" "math"
"math/big" "math/big"
"testing" "testing"
@ -336,32 +337,98 @@ func TestStorageDelete(t *testing.T) {
} }
func BenchmarkStorageFind(b *testing.B) { func BenchmarkStorageFind(b *testing.B) {
v, contractState, context, chain := createVMAndContractState(b) for count := 10; count <= 10000; count *= 10 {
require.NoError(b, chain.contracts.Management.PutContractState(chain.dao, contractState)) 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) b.ResetTimer()
for i := 0; i < count; i++ { b.ReportAllocs()
items["abc"+random.String(10)] = random.Bytes(10) for i := 0; i < b.N; i++ {
} b.StopTimer()
for k, v := range items { v.Estack().PushVal(istorage.FindDefault)
require.NoError(b, context.DAO.PutStorageItem(contractState.ID, []byte(k), v)) v.Estack().PushVal("abc")
require.NoError(b, context.DAO.PutStorageItem(contractState.ID+1, []byte(k), v)) v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ID: contractState.ID}))
b.StartTimer()
err := storageFind(context)
if err != nil {
b.FailNow()
}
}
})
} }
}
b.ResetTimer() func BenchmarkStorageFindIteratorNext(b *testing.B) {
b.ReportAllocs() for count := 10; count <= 10000; count *= 10 {
for i := 0; i < b.N; i++ { cases := map[string]int{
b.StopTimer() "Pick1": 1,
v.Estack().PushVal(istorage.FindDefault) "PickHalf": count / 2,
v.Estack().PushVal("abc") "PickAll": count,
v.Estack().PushVal(stackitem.NewInterop(&StorageContext{ID: contractState.ID}))
b.StartTimer()
err := storageFind(context)
if err != nil {
b.FailNow()
} }
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)
}
}
})
}
})
} }
} }

View file

@ -6,7 +6,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func newBadgerDBForTesting(t *testing.T) Store { func newBadgerDBForTesting(t testing.TB) Store {
bdbDir := t.TempDir() bdbDir := t.TempDir()
dbConfig := DBConfiguration{ dbConfig := DBConfiguration{
Type: "badgerdb", Type: "badgerdb",

View file

@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func newBoltStoreForTesting(t *testing.T) Store { func newBoltStoreForTesting(t testing.TB) Store {
d := t.TempDir() d := t.TempDir()
testFileName := path.Join(d, "test_bolt_db") testFileName := path.Join(d, "test_bolt_db")
boltDBStore, err := NewBoltDBStore(BoltDBOptions{FilePath: testFileName}) boltDBStore, err := NewBoltDBStore(BoltDBOptions{FilePath: testFileName})

View file

@ -6,7 +6,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func newLevelDBForTesting(t *testing.T) Store { func newLevelDBForTesting(t testing.TB) Store {
ldbDir := t.TempDir() ldbDir := t.TempDir()
dbConfig := DBConfiguration{ dbConfig := DBConfiguration{
Type: "leveldb", Type: "leveldb",

View file

@ -1,8 +1,10 @@
package storage package storage
import ( import (
"fmt"
"testing" "testing"
"github.com/nspcc-dev/neo-go/internal/random"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "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()) return NewMemCachedStore(NewMemoryStore())
} }

View file

@ -1,9 +1,35 @@
package storage package storage
import ( import (
"fmt"
"testing" "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() 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) {})
}
})
}
}

View file

@ -12,7 +12,7 @@ type mockedRedisStore struct {
mini *miniredis.Miniredis mini *miniredis.Miniredis
} }
func prepareRedisMock(t *testing.T) (*miniredis.Miniredis, *RedisStore) { func prepareRedisMock(t testing.TB) (*miniredis.Miniredis, *RedisStore) {
miniRedis, err := miniredis.Run() miniRedis, err := miniredis.Run()
require.Nil(t, err, "MiniRedis mock creation error") require.Nil(t, err, "MiniRedis mock creation error")
@ -37,7 +37,7 @@ func (mrs *mockedRedisStore) Close() error {
return err return err
} }
func newRedisStoreForTesting(t *testing.T) Store { func newRedisStoreForTesting(t testing.TB) Store {
mock, rs := prepareRedisMock(t) mock, rs := prepareRedisMock(t)
mrs := &mockedRedisStore{RedisStore: *rs, mini: mock} mrs := &mockedRedisStore{RedisStore: *rs, mini: mock}
return mrs return mrs

View file

@ -19,7 +19,7 @@ type kvSeen struct {
type dbSetup struct { type dbSetup struct {
name string name string
create func(*testing.T) Store create func(testing.TB) Store
} }
type dbTestFunction func(*testing.T, Store) type dbTestFunction func(*testing.T, Store)