From 1ea7e05cde70dcdbd61a04d7ca06cd33b5f439fd Mon Sep 17 00:00:00 2001 From: Fridrik Asmundsson Date: Mon, 20 Mar 2023 15:17:27 +0000 Subject: [PATCH 1/3] feat: Add small cache to execution traces This PR adds a small cache to calls to ExecutionTrace which helps improve performance for node operators like exchanges and block explorers. If items is in cache calls to this function will be 2-3x faster. Fixes: https://github.com/filecoin-project/lotus/issues/10504 --- chain/stmgr/execute.go | 7 +++++++ chain/stmgr/stmgr.go | 22 ++++++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/chain/stmgr/execute.go b/chain/stmgr/execute.go index f85ff7c043..3c26445508 100644 --- a/chain/stmgr/execute.go +++ b/chain/stmgr/execute.go @@ -128,10 +128,17 @@ func (sm *StateManager) ExecutionTraceWithMonitor(ctx context.Context, ts *types } func (sm *StateManager) ExecutionTrace(ctx context.Context, ts *types.TipSet) (cid.Cid, []*api.InvocResult, error) { + if entry, ok := sm.execTraceCache.Get(ts); ok { + return entry.cid, entry.invocTrace, nil + } + var invocTrace []*api.InvocResult st, err := sm.ExecutionTraceWithMonitor(ctx, ts, &InvocationTracer{trace: &invocTrace}) if err != nil { return cid.Undef, nil, err } + + sm.execTraceCache.Add(ts, tipSetCacheEntry{st, invocTrace}) + return st, invocTrace, nil } diff --git a/chain/stmgr/stmgr.go b/chain/stmgr/stmgr.go index 2d528c91be..5f0ad33fe5 100644 --- a/chain/stmgr/stmgr.go +++ b/chain/stmgr/stmgr.go @@ -5,6 +5,7 @@ import ( "fmt" "sync" + lru "github.com/hashicorp/golang-lru/v2" "github.com/ipfs/go-cid" dstore "github.com/ipfs/go-datastore" cbor "github.com/ipfs/go-ipld-cbor" @@ -39,6 +40,8 @@ import ( const LookbackNoLimit = api.LookbackNoLimit const ReceiptAmtBitwidth = 3 +const execTraceCacheSize = 16 + var log = logging.Logger("statemgr") type StateManagerAPI interface { @@ -138,6 +141,10 @@ type StateManager struct { beacon beacon.Schedule msgIndex index.MsgIndex + + // We keep a small cache for calls to ExecutionTrace which helps improve + // performance for node operators like exchanges and block explorers + execTraceCache *lru.ARCCache[*types.TipSet, tipSetCacheEntry] } // Caches a single state tree @@ -146,6 +153,11 @@ type treeCache struct { tree *state.StateTree } +type tipSetCacheEntry struct { + cid cid.Cid + invocTrace []*api.InvocResult +} + func NewStateManager(cs *store.ChainStore, exec Executor, sys vm.SyscallBuilder, us UpgradeSchedule, beacon beacon.Schedule, metadataDs dstore.Batching, msgIndex index.MsgIndex) (*StateManager, error) { // If we have upgrades, make sure they're in-order and make sense. if err := us.Validate(); err != nil { @@ -185,6 +197,11 @@ func NewStateManager(cs *store.ChainStore, exec Executor, sys vm.SyscallBuilder, } } + execTraceCache, err := lru.NewARC[*types.TipSet, tipSetCacheEntry](execTraceCacheSize) + if err != nil { + return nil, err + } + return &StateManager{ networkVersions: networkVersions, latestVersion: lastVersion, @@ -200,8 +217,9 @@ func NewStateManager(cs *store.ChainStore, exec Executor, sys vm.SyscallBuilder, root: cid.Undef, tree: nil, }, - compWait: make(map[string]chan struct{}), - msgIndex: msgIndex, + compWait: make(map[string]chan struct{}), + msgIndex: msgIndex, + execTraceCache: execTraceCache, }, nil } From f84f8a831a5dc11f7881dfb869993e7441aa54f6 Mon Sep 17 00:00:00 2001 From: Fridrik Asmundsson Date: Thu, 23 Mar 2023 13:14:49 +0000 Subject: [PATCH 2/3] Use TipSetKey as key in cache and return copies --- chain/stmgr/execute.go | 27 ++++++++++++++++++++++++--- chain/stmgr/stmgr.go | 9 +++++---- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/chain/stmgr/execute.go b/chain/stmgr/execute.go index 3c26445508..7613989283 100644 --- a/chain/stmgr/execute.go +++ b/chain/stmgr/execute.go @@ -128,8 +128,16 @@ func (sm *StateManager) ExecutionTraceWithMonitor(ctx context.Context, ts *types } func (sm *StateManager) ExecutionTrace(ctx context.Context, ts *types.TipSet) (cid.Cid, []*api.InvocResult, error) { - if entry, ok := sm.execTraceCache.Get(ts); ok { - return entry.cid, entry.invocTrace, nil + tsKey := ts.Key() + + { + // check if we have the trace for this tipset in the cache + sm.execTraceCacheLock.Lock() + defer sm.execTraceCacheLock.Unlock() + if entry, ok := sm.execTraceCache.Get(tsKey); ok { + // we have to make a deep copy since caller can modify the invocTrace + return entry.postStateRoot, makeDeepCopy(entry.invocTrace), nil + } } var invocTrace []*api.InvocResult @@ -138,7 +146,20 @@ func (sm *StateManager) ExecutionTrace(ctx context.Context, ts *types.TipSet) (c return cid.Undef, nil, err } - sm.execTraceCache.Add(ts, tipSetCacheEntry{st, invocTrace}) + sm.execTraceCache.Add(tsKey, tipSetCacheEntry{st, makeDeepCopy(invocTrace)}) return st, invocTrace, nil } + +func makeDeepCopy(invocTrace []*api.InvocResult) []*api.InvocResult { + c := make([]*api.InvocResult, len(invocTrace)) + for i, ir := range invocTrace { + if ir == nil { + continue + } + tmp := *ir + c[i] = &tmp + } + + return c +} diff --git a/chain/stmgr/stmgr.go b/chain/stmgr/stmgr.go index 5f0ad33fe5..b0876215e8 100644 --- a/chain/stmgr/stmgr.go +++ b/chain/stmgr/stmgr.go @@ -144,7 +144,8 @@ type StateManager struct { // We keep a small cache for calls to ExecutionTrace which helps improve // performance for node operators like exchanges and block explorers - execTraceCache *lru.ARCCache[*types.TipSet, tipSetCacheEntry] + execTraceCache *lru.ARCCache[types.TipSetKey, tipSetCacheEntry] + execTraceCacheLock sync.Mutex } // Caches a single state tree @@ -154,8 +155,8 @@ type treeCache struct { } type tipSetCacheEntry struct { - cid cid.Cid - invocTrace []*api.InvocResult + postStateRoot cid.Cid + invocTrace []*api.InvocResult } func NewStateManager(cs *store.ChainStore, exec Executor, sys vm.SyscallBuilder, us UpgradeSchedule, beacon beacon.Schedule, metadataDs dstore.Batching, msgIndex index.MsgIndex) (*StateManager, error) { @@ -197,7 +198,7 @@ func NewStateManager(cs *store.ChainStore, exec Executor, sys vm.SyscallBuilder, } } - execTraceCache, err := lru.NewARC[*types.TipSet, tipSetCacheEntry](execTraceCacheSize) + execTraceCache, err := lru.NewARC[types.TipSetKey, tipSetCacheEntry](execTraceCacheSize) if err != nil { return nil, err } From 2e45f6f7781c7ad5371bb63fe7db6bf98bd55e5e Mon Sep 17 00:00:00 2001 From: Fridrik Asmundsson Date: Sat, 25 Mar 2023 12:07:37 +0000 Subject: [PATCH 3/3] Dont do locking using defer --- chain/stmgr/execute.go | 23 ++++++++++++++--------- chain/stmgr/stmgr.go | 4 +++- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/chain/stmgr/execute.go b/chain/stmgr/execute.go index 7613989283..8c85a1031b 100644 --- a/chain/stmgr/execute.go +++ b/chain/stmgr/execute.go @@ -130,15 +130,16 @@ func (sm *StateManager) ExecutionTraceWithMonitor(ctx context.Context, ts *types func (sm *StateManager) ExecutionTrace(ctx context.Context, ts *types.TipSet) (cid.Cid, []*api.InvocResult, error) { tsKey := ts.Key() - { - // check if we have the trace for this tipset in the cache - sm.execTraceCacheLock.Lock() - defer sm.execTraceCacheLock.Unlock() - if entry, ok := sm.execTraceCache.Get(tsKey); ok { - // we have to make a deep copy since caller can modify the invocTrace - return entry.postStateRoot, makeDeepCopy(entry.invocTrace), nil - } + // check if we have the trace for this tipset in the cache + sm.execTraceCacheLock.Lock() + if entry, ok := sm.execTraceCache.Get(tsKey); ok { + // we have to make a deep copy since caller can modify the invocTrace + // and we don't want that to change what we store in cache + invocTraceCopy := makeDeepCopy(entry.invocTrace) + sm.execTraceCacheLock.Unlock() + return entry.postStateRoot, invocTraceCopy, nil } + sm.execTraceCacheLock.Unlock() var invocTrace []*api.InvocResult st, err := sm.ExecutionTraceWithMonitor(ctx, ts, &InvocationTracer{trace: &invocTrace}) @@ -146,7 +147,11 @@ func (sm *StateManager) ExecutionTrace(ctx context.Context, ts *types.TipSet) (c return cid.Undef, nil, err } - sm.execTraceCache.Add(tsKey, tipSetCacheEntry{st, makeDeepCopy(invocTrace)}) + invocTraceCopy := makeDeepCopy(invocTrace) + + sm.execTraceCacheLock.Lock() + sm.execTraceCache.Add(tsKey, tipSetCacheEntry{st, invocTraceCopy}) + sm.execTraceCacheLock.Unlock() return st, invocTrace, nil } diff --git a/chain/stmgr/stmgr.go b/chain/stmgr/stmgr.go index b0876215e8..bf10665e7d 100644 --- a/chain/stmgr/stmgr.go +++ b/chain/stmgr/stmgr.go @@ -144,7 +144,9 @@ type StateManager struct { // We keep a small cache for calls to ExecutionTrace which helps improve // performance for node operators like exchanges and block explorers - execTraceCache *lru.ARCCache[types.TipSetKey, tipSetCacheEntry] + execTraceCache *lru.ARCCache[types.TipSetKey, tipSetCacheEntry] + // We need a lock while making the copy as to prevent other callers + // overwrite the cache while making the copy execTraceCacheLock sync.Mutex }