Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
- [#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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- [#21](https://github.com/crypto-org-chain/cronos-store/pull/21) feat: rm not thread-safe cache.
- [#21](https://github.com/crypto-org-chain/cronos-store/pull/21) feat: remove cache because it is not thread-safe.

95 changes: 75 additions & 20 deletions memiavl/benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package memiavl
import (
"bytes"
"encoding/binary"
"fmt"
"math/rand"
"sort"
"testing"
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
}
Expand Down
57 changes: 8 additions & 49 deletions memiavl/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand All @@ -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() {
Expand Down Expand Up @@ -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
}

Expand All @@ -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
Expand Down Expand Up @@ -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
}

Expand Down
Loading