interop/vm: make VM reusable and use on VM for all in-block execs

Avoid allocating again and again. Increases TPS by about 3%.
This commit is contained in:
Roman Khimov 2022-06-06 22:48:10 +03:00
parent bdc6624c9d
commit 92c94f265c
3 changed files with 41 additions and 9 deletions

View file

@ -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,

View file

@ -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

View file

@ -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