-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Optimize iteration on nested cache context
Closes: #10310 Solution: - cache the valid status
- Loading branch information
Showing
2 changed files
with
178 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
package cachekv_test | ||
|
||
import ( | ||
fmt "fmt" | ||
"testing" | ||
|
||
"github.com/cosmos/cosmos-sdk/store" | ||
storetypes "github.com/cosmos/cosmos-sdk/store/types" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
"github.com/tendermint/tendermint/libs/log" | ||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types" | ||
dbm "github.com/tendermint/tm-db" | ||
) | ||
|
||
// cachedContext is a pair of cache context and its corresponding commit method. | ||
// They are obtained from the return value of `context.CacheContext()`. | ||
type cachedContext struct { | ||
ctx sdk.Context | ||
commit func() | ||
} | ||
|
||
// ContextStack manages the initial context and a stack of cached contexts, | ||
// to support the `StateDB.Snapshot` and `StateDB.RevertToSnapshot` methods. | ||
type ContextStack struct { | ||
// Context of the initial state before transaction execution. | ||
// It's the context used by `StateDB.CommitedState`. | ||
initialCtx sdk.Context | ||
cachedContexts []cachedContext | ||
} | ||
|
||
// CurrentContext returns the top context of cached stack, | ||
// if the stack is empty, returns the initial context. | ||
func (cs *ContextStack) CurrentContext() sdk.Context { | ||
l := len(cs.cachedContexts) | ||
if l == 0 { | ||
return cs.initialCtx | ||
} | ||
return cs.cachedContexts[l-1].ctx | ||
} | ||
|
||
// Reset sets the initial context and clear the cache context stack. | ||
func (cs *ContextStack) Reset(ctx sdk.Context) { | ||
cs.initialCtx = ctx | ||
if len(cs.cachedContexts) > 0 { | ||
cs.cachedContexts = []cachedContext{} | ||
} | ||
} | ||
|
||
// IsEmpty returns true if the cache context stack is empty. | ||
func (cs *ContextStack) IsEmpty() bool { | ||
return len(cs.cachedContexts) == 0 | ||
} | ||
|
||
// Commit commits all the cached contexts from top to bottom in order and clears the stack by setting an empty slice of cache contexts. | ||
func (cs *ContextStack) Commit() { | ||
// commit in order from top to bottom | ||
for i := len(cs.cachedContexts) - 1; i >= 0; i-- { | ||
// keep all the cosmos events | ||
cs.initialCtx.EventManager().EmitEvents(cs.cachedContexts[i].ctx.EventManager().Events()) | ||
if cs.cachedContexts[i].commit == nil { | ||
panic(fmt.Sprintf("commit function at index %d should not be nil", i)) | ||
} else { | ||
cs.cachedContexts[i].commit() | ||
} | ||
} | ||
cs.cachedContexts = []cachedContext{} | ||
} | ||
|
||
// CommitToRevision commit the cache after the target revision, | ||
// to improve efficiency of db operations. | ||
func (cs *ContextStack) CommitToRevision(target int) error { | ||
if target < 0 || target >= len(cs.cachedContexts) { | ||
return fmt.Errorf("snapshot index %d out of bound [%d..%d)", target, 0, len(cs.cachedContexts)) | ||
} | ||
|
||
targetCtx := cs.cachedContexts[target].ctx | ||
// commit in order from top to bottom | ||
for i := len(cs.cachedContexts) - 1; i > target; i-- { | ||
// keep all the cosmos events | ||
targetCtx.EventManager().EmitEvents(cs.cachedContexts[i].ctx.EventManager().Events()) | ||
if cs.cachedContexts[i].commit == nil { | ||
return fmt.Errorf("commit function at index %d should not be nil", i) | ||
} | ||
cs.cachedContexts[i].commit() | ||
} | ||
cs.cachedContexts = cs.cachedContexts[0 : target+1] | ||
|
||
return nil | ||
} | ||
|
||
// Snapshot pushes a new cached context to the stack, | ||
// and returns the index of it. | ||
func (cs *ContextStack) Snapshot() int { | ||
i := len(cs.cachedContexts) | ||
ctx, commit := cs.CurrentContext().CacheContext() | ||
cs.cachedContexts = append(cs.cachedContexts, cachedContext{ctx: ctx, commit: commit}) | ||
return i | ||
} | ||
|
||
// RevertToSnapshot pops all the cached contexts after the target index (inclusive). | ||
// the target should be snapshot index returned by `Snapshot`. | ||
// This function panics if the index is out of bounds. | ||
func (cs *ContextStack) RevertToSnapshot(target int) { | ||
if target < 0 || target >= len(cs.cachedContexts) { | ||
panic(fmt.Errorf("snapshot index %d out of bound [%d..%d)", target, 0, len(cs.cachedContexts))) | ||
} | ||
cs.cachedContexts = cs.cachedContexts[:target] | ||
} | ||
|
||
// RevertAll discards all the cache contexts. | ||
func (cs *ContextStack) RevertAll() { | ||
if len(cs.cachedContexts) > 0 { | ||
cs.RevertToSnapshot(0) | ||
} | ||
} | ||
func DoBenchmarkDeepContextStack(b *testing.B, depth int) { | ||
begin := []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} | ||
end := []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} | ||
key := storetypes.NewKVStoreKey("test") | ||
|
||
db := dbm.NewMemDB() | ||
cms := store.NewCommitMultiStore(db) | ||
cms.MountStoreWithDB(key, storetypes.StoreTypeIAVL, db) | ||
cms.LoadLatestVersion() | ||
ctx := sdk.NewContext(cms, tmproto.Header{}, false, log.NewNopLogger()) | ||
|
||
var stack ContextStack | ||
stack.Reset(ctx) | ||
|
||
for i := 0; i < depth; i++ { | ||
stack.Snapshot() | ||
|
||
store := stack.CurrentContext().KVStore(key) | ||
store.Set(begin, []byte("value")) | ||
} | ||
|
||
store := stack.CurrentContext().KVStore(key) | ||
for i := 0; i < b.N; i++ { | ||
store.Iterator(begin, end) | ||
} | ||
} | ||
|
||
func BenchmarkDeepContextStack1(b *testing.B) { | ||
DoBenchmarkDeepContextStack(b, 1) | ||
} | ||
|
||
func BenchmarkDeepContextStack3(b *testing.B) { | ||
DoBenchmarkDeepContextStack(b, 3) | ||
} | ||
func BenchmarkDeepContextStack10(b *testing.B) { | ||
DoBenchmarkDeepContextStack(b, 10) | ||
} | ||
|
||
func BenchmarkDeepContextStack13(b *testing.B) { | ||
DoBenchmarkDeepContextStack(b, 13) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters