From 92c94f265c3d69c4c1f1812546ce02406df6097f Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 6 Jun 2022 22:48:10 +0300 Subject: [PATCH] interop/vm: make VM reusable and use on VM for all in-block execs Avoid allocating again and again. Increases TPS by about 3%. --- pkg/core/blockchain.go | 20 ++++++++++++-------- pkg/core/interop/context.go | 12 +++++++++++- pkg/vm/vm.go | 18 ++++++++++++++++++ 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 6d895d8f3..694d99b33 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1095,7 +1095,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error close(aerdone) }() _ = cache.GetItemCtx() // Prime serialization context cache (it'll be reused by upper layer DAOs). - aer, err := bc.runPersist(bc.contracts.GetPersistScript(), block, cache, trigger.OnPersist) + aer, v, err := bc.runPersist(bc.contracts.GetPersistScript(), block, cache, trigger.OnPersist, nil) if err != nil { // Release goroutines, don't care about errors, we already have one. close(aerchan) @@ -1107,7 +1107,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error for _, tx := range block.Transactions { systemInterop := bc.newInteropContext(trigger.Application, cache, block, tx) - v := systemInterop.SpawnVM() + systemInterop.ReuseVM(v) v.LoadScriptWithFlags(tx.Script, callflag.All) v.GasLimit = tx.SystemFee @@ -1143,7 +1143,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error aerchan <- aer } - aer, err = bc.runPersist(bc.contracts.GetPostPersistScript(), block, cache, trigger.PostPersist) + aer, _, err = bc.runPersist(bc.contracts.GetPostPersistScript(), block, cache, trigger.PostPersist, v) if err != nil { // Release goroutines, don't care about errors, we already have one. close(aerchan) @@ -1278,14 +1278,18 @@ func (bc *Blockchain) IsExtensibleAllowed(u util.Uint160) bool { return n < len(us) } -func (bc *Blockchain) runPersist(script []byte, block *block.Block, cache *dao.Simple, trig trigger.Type) (*state.AppExecResult, error) { +func (bc *Blockchain) runPersist(script []byte, block *block.Block, cache *dao.Simple, trig trigger.Type, v *vm.VM) (*state.AppExecResult, *vm.VM, error) { systemInterop := bc.newInteropContext(trig, cache, block, nil) - v := systemInterop.SpawnVM() + if v == nil { + v = systemInterop.SpawnVM() + } else { + systemInterop.ReuseVM(v) + } v.LoadScriptWithFlags(script, callflag.All) if err := systemInterop.Exec(); err != nil { - return nil, fmt.Errorf("VM has failed: %w", err) + return nil, v, fmt.Errorf("VM has failed: %w", err) } else if _, err := systemInterop.DAO.Persist(); err != nil { - return nil, fmt.Errorf("can't save changes: %w", err) + return nil, v, fmt.Errorf("can't save changes: %w", err) } return &state.AppExecResult{ Container: block.Hash(), // application logs can be retrieved by block hash @@ -1296,7 +1300,7 @@ func (bc *Blockchain) runPersist(script []byte, block *block.Block, cache *dao.S Stack: v.Estack().ToArray(), Events: systemInterop.Notifications, }, - }, nil + }, v, nil } func (bc *Blockchain) handleNotification(note *state.NotificationEvent, d *dao.Simple, diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index 3421afb21..f3b805132 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -326,12 +326,22 @@ func (ic *Context) SyscallHandler(_ *vm.VM, id uint32) error { // SpawnVM spawns a new VM with the specified gas limit and set context.VM field. func (ic *Context) SpawnVM() *vm.VM { v := vm.NewWithTrigger(ic.Trigger) + ic.initVM(v) + return v +} + +func (ic *Context) initVM(v *vm.VM) { v.LoadToken = ic.LoadToken v.GasLimit = -1 v.SyscallHandler = ic.SyscallHandler v.SetPriceGetter(ic.GetPrice) ic.VM = v - return v +} + +// ReuseVM resets given VM and allows to reuse it in the current context. +func (ic *Context) ReuseVM(v *vm.VM) { + v.Reset(ic.Trigger) + ic.initVM(v) } // RegisterCancelFunc adds the given function to the list of functions to be called after the VM diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index d7b744aed..95e03a2c1 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -122,6 +122,24 @@ func (v *VM) SetPriceGetter(f func(opcode.Opcode, []byte) int64) { v.getPrice = f } +// Reset allows to reuse existing VM for subsequent executions making them somewhat +// more efficient. It reuses invocation and evaluation stacks as well as VM structure +// itself. +func (v *VM) Reset(t trigger.Type) { + v.state = NoneState + v.getPrice = nil + v.istack.elems = v.istack.elems[:0] + v.estack.elems = v.estack.elems[:0] + v.uncaughtException = nil + v.refs = 0 + v.gasConsumed = 0 + v.GasLimit = 0 + v.SyscallHandler = nil + v.LoadToken = nil + v.trigger = t + v.invTree = nil +} + // GasConsumed returns the amount of GAS consumed during execution. func (v *VM) GasConsumed() int64 { return v.gasConsumed