From cc16dac0b4f817f5c7a86bdaab842aa0309ea6c5 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 23 Dec 2019 18:02:39 +0300 Subject: [PATCH 1/2] vm: optimize script loading a bit Preseed the scriptHash value when we already know it. Eliminates this time waste from the pprof graph, but doesn't really change anything in the 1.4M -> 1.5M 100K mainnet blocks import test. --- pkg/vm/vm.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 204846f49..eae4aaab5 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -238,6 +238,14 @@ func (v *VM) LoadScript(b []byte) { v.istack.PushVal(ctx) } +// loadScriptWithHash if similar to the LoadScript method, but it also loads +// given script hash directly into the Context to avoid its recalculations. It's +// up to user of this function to make sure the script and hash match each other. +func (v *VM) loadScriptWithHash(b []byte, hash util.Uint160) { + v.LoadScript(b) + v.istack.Top().Value().(*Context).scriptHash = hash +} + // Context returns the current executed context. Nil if there is no context, // which implies no program is loaded. func (v *VM) Context() *Context { @@ -1120,7 +1128,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro _ = v.istack.Pop() } - v.LoadScript(script) + v.loadScriptWithHash(script, hash) case opcode.RET: oldCtx := v.istack.Pop().Value().(*Context) @@ -1336,6 +1344,7 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro panic(fmt.Sprintf("could not find script %s", hash)) } newCtx = NewContext(script) + newCtx.scriptHash = hash } newCtx.rvcount = rvcount newCtx.estack = NewStack("evaluation") From bf84e1f2fbfb20660dfeeebe73c6420334fd6ce4 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 23 Dec 2019 19:18:12 +0300 Subject: [PATCH 2/2] core: cache top block Turns out, our dApps use it a lot and we were going to the DB to get it which is a useless waste of time. Technically we could also remove blockHeight here, but not doing it at the moment as it's more involved. It eliminates this time waste from the pprof graph, but doesn't change 1.4M -> 1.5M 100K mainnet block import test case in any noticeable way. --- pkg/core/blockchain.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 18f589832..863da412d 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -55,6 +55,9 @@ type Blockchain struct { // Write access should only happen in storeBlock(). blockHeight uint32 + // Current top Block wrapped in an atomic.Value for safe access. + topBlock atomic.Value + // Current persisted block count. persistedHeight uint32 @@ -558,6 +561,7 @@ func (bc *Blockchain) storeBlock(block *Block) error { if err != nil { return err } + bc.topBlock.Store(block) atomic.StoreUint32(&bc.blockHeight, block.Index) updateBlockHeightMetric(block.Index) for _, tx := range block.Transactions { @@ -749,6 +753,13 @@ func (bc *Blockchain) GetStorageItems(hash util.Uint160) (map[string]*state.Stor // GetBlock returns a Block by the given hash. func (bc *Blockchain) GetBlock(hash util.Uint256) (*Block, error) { + topBlock := bc.topBlock.Load() + if topBlock != nil { + if tb, ok := topBlock.(*Block); ok && tb.Hash().Equals(hash) { + return tb, nil + } + } + block, err := bc.dao.GetBlock(hash) if err != nil { return nil, err @@ -768,6 +779,12 @@ func (bc *Blockchain) GetBlock(hash util.Uint256) (*Block, error) { // GetHeader returns data block header identified with the given hash value. func (bc *Blockchain) GetHeader(hash util.Uint256) (*Header, error) { + topBlock := bc.topBlock.Load() + if topBlock != nil { + if tb, ok := topBlock.(*Block); ok && tb.Hash().Equals(hash) { + return tb.Header(), nil + } + } block, err := bc.dao.GetBlock(hash) if err != nil { return nil, err