Merge PR #4265: CacheKVStore keep sorted items

This commit is contained in:
Joon 2019-05-15 16:42:06 +02:00 committed by Alexander Bezobchuk
parent d8bd7bcba8
commit dfef88dea8
5 changed files with 133 additions and 36 deletions

View File

@ -0,0 +1 @@
#2286 Improve performance of CacheKVStore iterator.

View File

@ -2,6 +2,7 @@ package cachekv
import ( import (
"bytes" "bytes"
"container/list"
cmn "github.com/tendermint/tendermint/libs/common" cmn "github.com/tendermint/tendermint/libs/common"
dbm "github.com/tendermint/tendermint/libs/db" dbm "github.com/tendermint/tendermint/libs/db"
@ -12,20 +13,30 @@ import (
// Implements Iterator. // Implements Iterator.
type memIterator struct { type memIterator struct {
start, end []byte start, end []byte
items []cmn.KVPair items []*cmn.KVPair
ascending bool
} }
func newMemIterator(start, end []byte, items []cmn.KVPair) *memIterator { func newMemIterator(start, end []byte, items *list.List, ascending bool) *memIterator {
itemsInDomain := make([]cmn.KVPair, 0) itemsInDomain := make([]*cmn.KVPair, 0)
for _, item := range items { var entered bool
if dbm.IsKeyInDomain(item.Key, start, end) { for e := items.Front(); e != nil; e = e.Next() {
itemsInDomain = append(itemsInDomain, item) item := e.Value.(*cmn.KVPair)
if !dbm.IsKeyInDomain(item.Key, start, end) {
if entered {
break
}
continue
} }
itemsInDomain = append(itemsInDomain, item)
entered = true
} }
return &memIterator{ return &memIterator{
start: start, start: start,
end: end, end: end,
items: itemsInDomain, items: itemsInDomain,
ascending: ascending,
} }
} }
@ -45,17 +56,27 @@ func (mi *memIterator) assertValid() {
func (mi *memIterator) Next() { func (mi *memIterator) Next() {
mi.assertValid() mi.assertValid()
mi.items = mi.items[1:] if mi.ascending {
mi.items = mi.items[1:]
} else {
mi.items = mi.items[:len(mi.items)-1]
}
} }
func (mi *memIterator) Key() []byte { func (mi *memIterator) Key() []byte {
mi.assertValid() mi.assertValid()
return mi.items[0].Key if mi.ascending {
return mi.items[0].Key
}
return mi.items[len(mi.items)-1].Key
} }
func (mi *memIterator) Value() []byte { func (mi *memIterator) Value() []byte {
mi.assertValid() mi.assertValid()
return mi.items[0].Value if mi.ascending {
return mi.items[0].Value
}
return mi.items[len(mi.items)-1].Value
} }
func (mi *memIterator) Close() { func (mi *memIterator) Close() {

View File

@ -2,6 +2,7 @@ package cachekv
import ( import (
"bytes" "bytes"
"container/list"
"io" "io"
"sort" "sort"
"sync" "sync"
@ -24,9 +25,11 @@ type cValue struct {
// Store wraps an in-memory cache around an underlying types.KVStore. // Store wraps an in-memory cache around an underlying types.KVStore.
type Store struct { type Store struct {
mtx sync.Mutex mtx sync.Mutex
cache map[string]cValue cache map[string]*cValue
parent types.KVStore unsortedCache map[string]struct{}
sortedCache *list.List // always ascending sorted
parent types.KVStore
} }
var _ types.CacheKVStore = (*Store)(nil) var _ types.CacheKVStore = (*Store)(nil)
@ -34,8 +37,10 @@ var _ types.CacheKVStore = (*Store)(nil)
// nolint // nolint
func NewStore(parent types.KVStore) *Store { func NewStore(parent types.KVStore) *Store {
return &Store{ return &Store{
cache: make(map[string]cValue), cache: make(map[string]*cValue),
parent: parent, unsortedCache: make(map[string]struct{}),
sortedCache: list.New(),
parent: parent,
} }
} }
@ -116,7 +121,9 @@ func (store *Store) Write() {
} }
// Clear the cache // Clear the cache
store.cache = make(map[string]cValue) store.cache = make(map[string]*cValue)
store.unsortedCache = make(map[string]struct{})
store.sortedCache = list.New()
} }
//---------------------------------------- //----------------------------------------
@ -146,6 +153,9 @@ func (store *Store) ReverseIterator(start, end []byte) types.Iterator {
} }
func (store *Store) iterator(start, end []byte, ascending bool) types.Iterator { func (store *Store) iterator(start, end []byte, ascending bool) types.Iterator {
store.mtx.Lock()
defer store.mtx.Unlock()
var parent, cache types.Iterator var parent, cache types.Iterator
if ascending { if ascending {
@ -154,33 +164,49 @@ func (store *Store) iterator(start, end []byte, ascending bool) types.Iterator {
parent = store.parent.ReverseIterator(start, end) parent = store.parent.ReverseIterator(start, end)
} }
items := store.dirtyItems(start, end, ascending) store.dirtyItems(start, end)
cache = newMemIterator(start, end, items) cache = newMemIterator(start, end, store.sortedCache, ascending)
return newCacheMergeIterator(parent, cache, ascending) return newCacheMergeIterator(parent, cache, ascending)
} }
// Constructs a slice of dirty items, to use w/ memIterator. // Constructs a slice of dirty items, to use w/ memIterator.
func (store *Store) dirtyItems(start, end []byte, ascending bool) []cmn.KVPair { func (store *Store) dirtyItems(start, end []byte) {
items := make([]cmn.KVPair, 0) unsorted := make([]*cmn.KVPair, 0)
for key, cacheValue := range store.cache { for key := range store.unsortedCache {
if !cacheValue.dirty { cacheValue := store.cache[key]
continue
}
if dbm.IsKeyInDomain([]byte(key), start, end) { if dbm.IsKeyInDomain([]byte(key), start, end) {
items = append(items, cmn.KVPair{Key: []byte(key), Value: cacheValue.value}) unsorted = append(unsorted, &cmn.KVPair{Key: []byte(key), Value: cacheValue.value})
delete(store.unsortedCache, key)
} }
} }
sort.Slice(items, func(i, j int) bool { sort.Slice(unsorted, func(i, j int) bool {
if ascending { return bytes.Compare(unsorted[i].Key, unsorted[j].Key) < 0
return bytes.Compare(items[i].Key, items[j].Key) < 0
}
return bytes.Compare(items[i].Key, items[j].Key) > 0
}) })
return items for e := store.sortedCache.Front(); e != nil && len(unsorted) != 0; {
uitem := unsorted[0]
sitem := e.Value.(*cmn.KVPair)
comp := bytes.Compare(uitem.Key, sitem.Key)
switch comp {
case -1:
unsorted = unsorted[1:]
store.sortedCache.InsertBefore(uitem, e)
case 1:
e = e.Next()
case 0:
unsorted = unsorted[1:]
e.Value = uitem
e = e.Next()
}
}
for _, kvp := range unsorted {
store.sortedCache.PushBack(kvp)
}
} }
//---------------------------------------- //----------------------------------------
@ -188,9 +214,12 @@ func (store *Store) dirtyItems(start, end []byte, ascending bool) []cmn.KVPair {
// Only entrypoint to mutate store.cache. // Only entrypoint to mutate store.cache.
func (store *Store) setCacheValue(key, value []byte, deleted bool, dirty bool) { func (store *Store) setCacheValue(key, value []byte, deleted bool, dirty bool) {
store.cache[string(key)] = cValue{ store.cache[string(key)] = &cValue{
value: value, value: value,
deleted: deleted, deleted: deleted,
dirty: dirty, dirty: dirty,
} }
if dirty {
store.unsortedCache[string(key)] = struct{}{}
}
} }

View File

@ -0,0 +1,46 @@
package cachekv_test
import (
"crypto/rand"
"sort"
"testing"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/cosmos/cosmos-sdk/store/cachekv"
"github.com/cosmos/cosmos-sdk/store/dbadapter"
)
func benchmarkCacheKVStoreIterator(numKVs int, b *testing.B) {
mem := dbadapter.Store{DB: dbm.NewMemDB()}
cstore := cachekv.NewStore(mem)
keys := make([]string, numKVs, numKVs)
for i := 0; i < numKVs; i++ {
key := make([]byte, 32)
value := make([]byte, 32)
_, _ = rand.Read(key)
_, _ = rand.Read(value)
keys[i] = string(key)
cstore.Set(key, value)
}
sort.Strings(keys)
for n := 0; n < b.N; n++ {
iter := cstore.Iterator([]byte(keys[0]), []byte(keys[numKVs-1]))
for _ = iter.Key(); iter.Valid(); iter.Next() {
}
iter.Close()
}
}
func BenchmarkCacheKVStoreIterator500(b *testing.B) { benchmarkCacheKVStoreIterator(500, b) }
func BenchmarkCacheKVStoreIterator1000(b *testing.B) { benchmarkCacheKVStoreIterator(1000, b) }
func BenchmarkCacheKVStoreIterator10000(b *testing.B) { benchmarkCacheKVStoreIterator(10000, b) }
func BenchmarkCacheKVStoreIterator50000(b *testing.B) { benchmarkCacheKVStoreIterator(50000, b) }
func BenchmarkCacheKVStoreIterator100000(b *testing.B) { benchmarkCacheKVStoreIterator(100000, b) }

View File

@ -293,8 +293,8 @@ func TestCacheKVMergeIteratorRandom(t *testing.T) {
st := newCacheKVStore() st := newCacheKVStore()
truth := dbm.NewMemDB() truth := dbm.NewMemDB()
start, end := 25, 75 start, end := 25, 975
max := 100 max := 1000
setRange(st, truth, start, end) setRange(st, truth, start, end)
// do an op, test the iterator // do an op, test the iterator