diff --git a/CHANGELOG.md b/CHANGELOG.md index bd6da79..763362d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ - [#1](https://github.com/crypto-org-chain/cronos-store/pull/1) feature: add store, memiavl, versiondb. - [#3](https://github.com/crypto-org-chain/cronos-store/pull/3) feat(memiavl): MultiTree add chainId. - [#4](https://github.com/crypto-org-chain/cronos-store/pull/4) feat(versiondb/client): add dump versiondb changeset cmd. -- [#7](https://github.com/crypto-org-chain/cronos-store/pull/7) fix: memiavl WriteSnapshotWithContext cancel using wrong ctx. \ No newline at end of file +- [#7](https://github.com/crypto-org-chain/cronos-store/pull/7) fix: memiavl WriteSnapshotWithContext cancel using wrong ctx. +- [#21](https://github.com/crypto-org-chain/cronos-store/pull/21) feat: rm not thread-safe cache. \ No newline at end of file diff --git a/memiavl/benchmark_test.go b/memiavl/benchmark_test.go index 71255ff..58919c6 100644 --- a/memiavl/benchmark_test.go +++ b/memiavl/benchmark_test.go @@ -3,6 +3,7 @@ package memiavl import ( "bytes" "encoding/binary" + "fmt" "math/rand" "sort" "testing" @@ -55,26 +56,6 @@ func BenchmarkRandomGet(b *testing.B) { _ = diskTree.Get(targetKey) } }) - b.Run("memiavl-disk-cache-hit", func(b *testing.B) { - diskTree := NewFromSnapshot(snapshot, true, 1) - require.Equal(b, targetValue, diskTree.Get(targetKey)) - - b.ResetTimer() - for i := 0; i < b.N; i++ { - _ = diskTree.Get(targetKey) - } - }) - b.Run("memiavl-disk-cache-miss", func(b *testing.B) { - diskTree := NewFromSnapshot(snapshot, true, 0) - // enforce an empty cache to emulate cache miss - diskTree.cache = iavlcache.New(0) - require.Equal(b, targetValue, diskTree.Get(targetKey)) - - b.ResetTimer() - for i := 0; i < b.N; i++ { - _ = diskTree.Get(targetKey) - } - }) b.Run("btree-degree-2", func(b *testing.B) { bt2 := btree.NewBTreeGOptions(lessG, btree.Options{ NoLocks: true, @@ -186,6 +167,80 @@ func BenchmarkRandomSet(b *testing.B) { }) } +func BenchmarkTreeGet(b *testing.B) { + benchmarkTreeGet(b, false) +} + +func BenchmarkTreeGetParallel(b *testing.B) { + benchmarkTreeGet(b, true) +} + +func benchmarkTreeGet(b *testing.B, parallel bool) { + b.Helper() + const keyCount = 1 << 15 + value := []byte("value") + cacheSizes := []int{0, 1024, 16 * 1024} + for _, cacheSize := range cacheSizes { + b.Run(fmt.Sprintf("cache=%d", cacheSize), func(b *testing.B) { + tree := New(cacheSize) + keys := make([][]byte, keyCount) + for i := 0; i < keyCount; i++ { + key := make([]byte, 8) + binary.BigEndian.PutUint64(key, uint64(i)) + keys[i] = key + tree.set(key, value) + } + + mask := keyCount - 1 + b.ResetTimer() + if parallel { + b.RunParallel(func(pb *testing.PB) { + idx := 0 + for pb.Next() { + key := keys[idx&mask] + if tree.Get(key) == nil { + panic("unexpected cache miss") + } + idx++ + } + }) + return + } + + for i := 0; i < b.N; i++ { + key := keys[i&mask] + if tree.Get(key) == nil { + panic("unexpected cache miss") + } + } + }) + } +} + +func BenchmarkTreeSet(b *testing.B) { + const keyCount = 1 << 14 + value := []byte("value") + cacheSizes := []int{0, 1024, 16 * 1024} + for _, cacheSize := range cacheSizes { + b.Run(fmt.Sprintf("cache=%d", cacheSize), func(b *testing.B) { + tree := New(cacheSize) + keys := make([][]byte, keyCount) + for i := 0; i < keyCount; i++ { + key := make([]byte, 8) + binary.BigEndian.PutUint64(key, uint64(i)) + keys[i] = key + } + + mask := keyCount - 1 + b.ResetTimer() + for i := 0; i < b.N; i++ { + idx := i & mask + tree.set(keys[idx], value) + } + }) + } +} + type itemT struct { key, value []byte } diff --git a/memiavl/tree.go b/memiavl/tree.go index b462e00..857cb57 100644 --- a/memiavl/tree.go +++ b/memiavl/tree.go @@ -5,43 +5,22 @@ import ( "crypto/sha256" "fmt" "math" - - "github.com/cosmos/iavl/cache" ) var emptyHash = sha256.New().Sum(nil) -func NewCache(cacheSize int) cache.Cache { - if cacheSize == 0 { - return nil - } - return cache.New(cacheSize) -} - // Tree verify change sets by replay them to rebuild iavl tree and verify the root hashes type Tree struct { version, cowVersion uint32 // root node of empty tree is represented as `nil` root Node snapshot *Snapshot - - // simple lru cache provided by iavl library - cache cache.Cache - // when true, the get and iterator methods could return a slice pointing to mmaped blob files. zeroCopy bool } -type cacheNode struct { - key, value []byte -} - -func (n *cacheNode) GetKey() []byte { - return n.key -} - // NewEmptyTree creates an empty tree at an arbitrary version. -func NewEmptyTree(version uint64, cacheSize int) *Tree { +func NewEmptyTree(version uint64, _ int) *Tree { if version >= math.MaxUint32 { panic("version overflows uint32") } @@ -50,31 +29,29 @@ func NewEmptyTree(version uint64, cacheSize int) *Tree { version: uint32(version), // no need to copy if the tree is not backed by snapshot zeroCopy: true, - cache: NewCache(cacheSize), } } // New creates an empty tree at genesis version -func New(cacheSize int) *Tree { - return NewEmptyTree(0, cacheSize) +func New(_ int) *Tree { + return NewEmptyTree(0, 0) } // NewWithInitialVersion creates a empty tree with initial-version, // it happens when a new store created at the middle of the chain. -func NewWithInitialVersion(initialVersion uint32, cacheSize int) *Tree { +func NewWithInitialVersion(initialVersion uint32, _ int) *Tree { if initialVersion <= 1 { - return New(cacheSize) + return New(0) } - return NewEmptyTree(uint64(initialVersion-1), cacheSize) + return NewEmptyTree(uint64(initialVersion-1), 0) } // NewFromSnapshot mmap the blob files and create the root node. -func NewFromSnapshot(snapshot *Snapshot, zeroCopy bool, cacheSize int) *Tree { +func NewFromSnapshot(snapshot *Snapshot, zeroCopy bool, _ int) *Tree { tree := &Tree{ version: snapshot.Version(), snapshot: snapshot, zeroCopy: zeroCopy, - cache: NewCache(cacheSize), } if !snapshot.IsEmpty() { @@ -116,14 +93,12 @@ func (t *Tree) setInitialVersion(initialVersion uint32) { // Copy returns a snapshot of the tree which won't be modified by further modifications on the main tree, // the returned new tree can be accessed concurrently with the main tree. -func (t *Tree) Copy(cacheSize int) *Tree { +func (t *Tree) Copy(_ int) *Tree { if _, ok := t.root.(*MemNode); ok { // protect the existing `MemNode`s from get modified in-place t.cowVersion = t.version } newTree := *t - // cache is not copied along because it's not thread-safe to access - newTree.cache = NewCache(cacheSize) return &newTree } @@ -144,16 +119,10 @@ func (t *Tree) set(key, value []byte) { value = []byte{} } t.root, _ = setRecursive(t.root, key, value, t.version+1, t.cowVersion) - if t.cache != nil { - t.cache.Add(&cacheNode{key, value}) - } } func (t *Tree) remove(key []byte) { _, t.root, _ = removeRecursive(t.root, key, t.version+1, t.cowVersion) - if t.cache != nil { - t.cache.Remove(key) - } } // SaveVersion increases the version number and optionally updates the hashes @@ -214,20 +183,10 @@ func (t *Tree) GetByIndex(index int64) ([]byte, []byte) { } func (t *Tree) Get(key []byte) []byte { - if t.cache != nil { - if node := t.cache.Get(key); node != nil { - return node.(*cacheNode).value - } - } - _, value := t.GetWithIndex(key) if value == nil { return nil } - - if t.cache != nil { - t.cache.Add(&cacheNode{key, value}) - } return value }