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) close(aerdone)
}() }()
_ = cache.GetItemCtx() // Prime serialization context cache (it'll be reused by upper layer DAOs). _ = 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 { if err != nil {
// Release goroutines, don't care about errors, we already have one. // Release goroutines, don't care about errors, we already have one.
close(aerchan) close(aerchan)
@ -1107,7 +1107,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
for _, tx := range block.Transactions { for _, tx := range block.Transactions {
systemInterop := bc.newInteropContext(trigger.Application, cache, block, tx) systemInterop := bc.newInteropContext(trigger.Application, cache, block, tx)
v := systemInterop.SpawnVM() systemInterop.ReuseVM(v)
v.LoadScriptWithFlags(tx.Script, callflag.All) v.LoadScriptWithFlags(tx.Script, callflag.All)
v.GasLimit = tx.SystemFee v.GasLimit = tx.SystemFee
@ -1143,7 +1143,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
aerchan <- aer 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 { if err != nil {
// Release goroutines, don't care about errors, we already have one. // Release goroutines, don't care about errors, we already have one.
close(aerchan) close(aerchan)
@ -1278,14 +1278,18 @@ func (bc *Blockchain) IsExtensibleAllowed(u util.Uint160) bool {
return n < len(us) 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) 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) v.LoadScriptWithFlags(script, callflag.All)
if err := systemInterop.Exec(); err != nil { 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 { } 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{ return &state.AppExecResult{
Container: block.Hash(), // application logs can be retrieved by block hash 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(), Stack: v.Estack().ToArray(),
Events: systemInterop.Notifications, Events: systemInterop.Notifications,
}, },
}, nil }, v, nil
} }
func (bc *Blockchain) handleNotification(note *state.NotificationEvent, d *dao.Simple, 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. // SpawnVM spawns a new VM with the specified gas limit and set context.VM field.
func (ic *Context) SpawnVM() *vm.VM { func (ic *Context) SpawnVM() *vm.VM {
v := vm.NewWithTrigger(ic.Trigger) v := vm.NewWithTrigger(ic.Trigger)
ic.initVM(v)
return v
}
func (ic *Context) initVM(v *vm.VM) {
v.LoadToken = ic.LoadToken v.LoadToken = ic.LoadToken
v.GasLimit = -1 v.GasLimit = -1
v.SyscallHandler = ic.SyscallHandler v.SyscallHandler = ic.SyscallHandler
v.SetPriceGetter(ic.GetPrice) v.SetPriceGetter(ic.GetPrice)
ic.VM = v 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 // 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 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. // GasConsumed returns the amount of GAS consumed during execution.
func (v *VM) GasConsumed() int64 { func (v *VM) GasConsumed() int64 {
return v.gasConsumed return v.gasConsumed