Merge pull request #2543 from nspcc-dev/perf-new

Minor allocation improvements
This commit is contained in:
Roman Khimov 2022-06-07 11:04:59 +03:00 committed by GitHub
commit 8673d2a79c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 118 additions and 63 deletions

View file

@ -202,7 +202,7 @@ func TestAppCall(t *testing.T) {
fc := fakechain.NewFakeChain() fc := fakechain.NewFakeChain()
ic := interop.NewContext(trigger.Application, fc, dao.NewSimple(storage.NewMemoryStore(), false, false), ic := interop.NewContext(trigger.Application, fc, dao.NewSimple(storage.NewMemoryStore(), false, false),
interop.DefaultBaseExecFee, native.DefaultStoragePrice, contractGetter, nil, nil, nil, zaptest.NewLogger(t)) interop.DefaultBaseExecFee, native.DefaultStoragePrice, contractGetter, nil, nil, nil, nil, zaptest.NewLogger(t))
t.Run("valid script", func(t *testing.T) { t.Run("valid script", func(t *testing.T) {
src := getAppCallScript(fmt.Sprintf("%#v", ih.BytesBE())) src := getAppCallScript(fmt.Sprintf("%#v", ih.BytesBE()))

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,10 +1107,8 @@ 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.SetPriceGetter(systemInterop.GetPrice)
v.LoadToken = contract.LoadToken(systemInterop)
v.GasLimit = tx.SystemFee v.GasLimit = tx.SystemFee
err := systemInterop.Exec() err := systemInterop.Exec()
@ -1145,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)
@ -1280,15 +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)
v.SetPriceGetter(systemInterop.GetPrice)
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
@ -1299,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,
@ -1365,6 +1366,7 @@ func (bc *Blockchain) processTokenTransfer(cache *dao.Simple, transCache map[uti
if !isNEP11 { if !isNEP11 {
nep17xfer = &state.NEP17Transfer{ nep17xfer = &state.NEP17Transfer{
Asset: id, Asset: id,
Amount: *amount,
From: from, From: from,
To: to, To: to,
Block: b.Index, Block: b.Index,
@ -1376,6 +1378,7 @@ func (bc *Blockchain) processTokenTransfer(cache *dao.Simple, transCache map[uti
nep11xfer := &state.NEP11Transfer{ nep11xfer := &state.NEP11Transfer{
NEP17Transfer: state.NEP17Transfer{ NEP17Transfer: state.NEP17Transfer{
Asset: id, Asset: id,
Amount: *amount,
From: from, From: from,
To: to, To: to,
Block: b.Index, Block: b.Index,
@ -1388,13 +1391,14 @@ func (bc *Blockchain) processTokenTransfer(cache *dao.Simple, transCache map[uti
nep17xfer = &nep11xfer.NEP17Transfer nep17xfer = &nep11xfer.NEP17Transfer
} }
if !from.Equals(util.Uint160{}) { if !from.Equals(util.Uint160{}) {
_ = nep17xfer.Amount.Neg(amount) // We already have the Int. _ = nep17xfer.Amount.Neg(&nep17xfer.Amount)
if appendTokenTransfer(cache, transCache, from, transfer, id, b.Index, b.Timestamp, isNEP11) != nil { err := appendTokenTransfer(cache, transCache, from, transfer, id, b.Index, b.Timestamp, isNEP11)
_ = nep17xfer.Amount.Neg(&nep17xfer.Amount)
if err != nil {
return return
} }
} }
if !to.Equals(util.Uint160{}) { if !to.Equals(util.Uint160{}) {
_ = nep17xfer.Amount.Set(amount) // We already have the Int.
_ = appendTokenTransfer(cache, transCache, to, transfer, id, b.Index, b.Timestamp, isNEP11) // Nothing useful we can do. _ = appendTokenTransfer(cache, transCache, to, transfer, id, b.Index, b.Timestamp, isNEP11) // Nothing useful we can do.
} }
} }
@ -1451,7 +1455,7 @@ func appendTokenTransfer(cache *dao.Simple, transCache map[util.Uint160]transfer
*nextBatch++ *nextBatch++
*currTimestamp = bTimestamp *currTimestamp = bTimestamp
// Put makes a copy of it anyway. // Put makes a copy of it anyway.
log.Raw = log.Raw[:0] log.Reset()
} }
transCache[addr] = transferData transCache[addr] = transferData
return nil return nil
@ -2164,9 +2168,7 @@ func (bc *Blockchain) GetEnrollments() ([]state.Validator, error) {
// GetTestVM returns an interop context with VM set up for a test run. // GetTestVM returns an interop context with VM set up for a test run.
func (bc *Blockchain) GetTestVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) *interop.Context { func (bc *Blockchain) GetTestVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) *interop.Context {
systemInterop := bc.newInteropContext(t, bc.dao, b, tx) systemInterop := bc.newInteropContext(t, bc.dao, b, tx)
vm := systemInterop.SpawnVM() _ = systemInterop.SpawnVM() // All the other code suppose that the VM is ready.
vm.SetPriceGetter(systemInterop.GetPrice)
vm.LoadToken = contract.LoadToken(systemInterop)
return systemInterop return systemInterop
} }
@ -2199,9 +2201,7 @@ func (bc *Blockchain) GetTestHistoricVM(t trigger.Type, tx *transaction.Transact
return nil, fmt.Errorf("failed to initialize native cache backed by historic DAO: %w", err) return nil, fmt.Errorf("failed to initialize native cache backed by historic DAO: %w", err)
} }
systemInterop := bc.newInteropContext(t, dTrie, b, tx) systemInterop := bc.newInteropContext(t, dTrie, b, tx)
vm := systemInterop.SpawnVM() _ = systemInterop.SpawnVM() // All the other code suppose that the VM is ready.
vm.SetPriceGetter(systemInterop.GetPrice)
vm.LoadToken = contract.LoadToken(systemInterop)
return systemInterop, nil return systemInterop, nil
} }
@ -2276,8 +2276,6 @@ func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transa
} }
vm := interopCtx.SpawnVM() vm := interopCtx.SpawnVM()
vm.SetPriceGetter(interopCtx.GetPrice)
vm.LoadToken = contract.LoadToken(interopCtx)
vm.GasLimit = gas vm.GasLimit = gas
if err := bc.InitVerificationContext(interopCtx, hash, witness); err != nil { if err := bc.InitVerificationContext(interopCtx, hash, witness); err != nil {
return 0, err return 0, err
@ -2373,7 +2371,7 @@ func (bc *Blockchain) newInteropContext(trigger trigger.Type, d *dao.Simple, blo
// changes that were not yet persisted to Blockchain's dao. // changes that were not yet persisted to Blockchain's dao.
baseStorageFee = bc.contracts.Policy.GetStoragePriceInternal(d) baseStorageFee = bc.contracts.Policy.GetStoragePriceInternal(d)
} }
ic := interop.NewContext(trigger, bc, d, baseExecFee, baseStorageFee, bc.contracts.Management.GetContract, bc.contracts.Contracts, block, tx, bc.log) ic := interop.NewContext(trigger, bc, d, baseExecFee, baseStorageFee, bc.contracts.Management.GetContract, bc.contracts.Contracts, contract.LoadToken, block, tx, bc.log)
ic.Functions = systemInterops ic.Functions = systemInterops
switch { switch {
case tx != nil: case tx != nil:

View file

@ -64,6 +64,7 @@ type Context struct {
getContract func(*dao.Simple, util.Uint160) (*state.Contract, error) getContract func(*dao.Simple, util.Uint160) (*state.Contract, error)
baseExecFee int64 baseExecFee int64
baseStorageFee int64 baseStorageFee int64
loadToken func(ic *Context, id int32) error
GetRandomCounter uint32 GetRandomCounter uint32
signers []transaction.Signer signers []transaction.Signer
} }
@ -71,6 +72,7 @@ type Context struct {
// NewContext returns new interop context. // NewContext returns new interop context.
func NewContext(trigger trigger.Type, bc Ledger, d *dao.Simple, baseExecFee, baseStorageFee int64, func NewContext(trigger trigger.Type, bc Ledger, d *dao.Simple, baseExecFee, baseStorageFee int64,
getContract func(*dao.Simple, util.Uint160) (*state.Contract, error), natives []Contract, getContract func(*dao.Simple, util.Uint160) (*state.Contract, error), natives []Contract,
loadTokenFunc func(ic *Context, id int32) error,
block *block.Block, tx *transaction.Transaction, log *zap.Logger) *Context { block *block.Block, tx *transaction.Transaction, log *zap.Logger) *Context {
dao := d.GetPrivate() dao := d.GetPrivate()
cfg := bc.GetConfig() cfg := bc.GetConfig()
@ -88,6 +90,7 @@ func NewContext(trigger trigger.Type, bc Ledger, d *dao.Simple, baseExecFee, bas
getContract: getContract, getContract: getContract,
baseExecFee: baseExecFee, baseExecFee: baseExecFee,
baseStorageFee: baseStorageFee, baseStorageFee: baseStorageFee,
loadToken: loadTokenFunc,
} }
} }
@ -298,6 +301,12 @@ func (ic *Context) BaseStorageFee() int64 {
return ic.baseStorageFee return ic.baseStorageFee
} }
// LoadToken wraps externally provided load-token loading function providing it with context,
// this function can then be easily used by VM.
func (ic *Context) LoadToken(id int32) error {
return ic.loadToken(ic, id)
}
// SyscallHandler handles syscall with id. // SyscallHandler handles syscall with id.
func (ic *Context) SyscallHandler(_ *vm.VM, id uint32) error { func (ic *Context) SyscallHandler(_ *vm.VM, id uint32) error {
f := ic.GetFunction(id) f := ic.GetFunction(id)
@ -317,10 +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.GasLimit = -1 v.GasLimit = -1
v.SyscallHandler = ic.SyscallHandler v.SyscallHandler = ic.SyscallHandler
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

@ -22,26 +22,24 @@ type policyChecker interface {
} }
// LoadToken calls method specified by the token id. // LoadToken calls method specified by the token id.
func LoadToken(ic *interop.Context) func(id int32) error { func LoadToken(ic *interop.Context, id int32) error {
return func(id int32) error { ctx := ic.VM.Context()
ctx := ic.VM.Context() if !ctx.GetCallFlags().Has(callflag.ReadStates | callflag.AllowCall) {
if !ctx.GetCallFlags().Has(callflag.ReadStates | callflag.AllowCall) { return errors.New("invalid call flags")
return errors.New("invalid call flags")
}
tok := ctx.NEF.Tokens[id]
if int(tok.ParamCount) > ctx.Estack().Len() {
return errors.New("stack is too small")
}
args := make([]stackitem.Item, tok.ParamCount)
for i := range args {
args[i] = ic.VM.Estack().Pop().Item()
}
cs, err := ic.GetContract(tok.Hash)
if err != nil {
return fmt.Errorf("token contract %s not found: %w", tok.Hash.StringLE(), err)
}
return callInternal(ic, cs, tok.Method, tok.CallFlag, tok.HasReturn, args, false)
} }
tok := ctx.NEF.Tokens[id]
if int(tok.ParamCount) > ctx.Estack().Len() {
return errors.New("stack is too small")
}
args := make([]stackitem.Item, tok.ParamCount)
for i := range args {
args[i] = ic.VM.Estack().Pop().Item()
}
cs, err := ic.GetContract(tok.Hash)
if err != nil {
return fmt.Errorf("token contract %s not found: %w", tok.Hash.StringLE(), err)
}
return callInternal(ic, cs, tok.Method, tok.CallFlag, tok.HasReturn, args, false)
} }
// Call calls a contract with flags. // Call calls a contract with flags.

View file

@ -73,7 +73,7 @@ func initCheckMultisigVMNoArgs(container *transaction.Transaction) *vm.VM {
trigger.Verification, trigger.Verification,
fakechain.NewFakeChain(), fakechain.NewFakeChain(),
dao.NewSimple(storage.NewMemoryStore(), false, false), dao.NewSimple(storage.NewMemoryStore(), false, false),
interop.DefaultBaseExecFee, native.DefaultStoragePrice, nil, nil, nil, interop.DefaultBaseExecFee, native.DefaultStoragePrice, nil, nil, nil, nil,
container, container,
nil) nil)
ic.Container = container ic.Container = container

View file

@ -16,6 +16,8 @@ const TokenTransferBatchSize = 128
// TokenTransferLog is a serialized log of token transfers. // TokenTransferLog is a serialized log of token transfers.
type TokenTransferLog struct { type TokenTransferLog struct {
Raw []byte Raw []byte
buf *bytes.Buffer
iow *io.BinWriter
} }
// NEP17Transfer represents a single NEP-17 Transfer event. // NEP17Transfer represents a single NEP-17 Transfer event.
@ -111,18 +113,30 @@ func (lg *TokenTransferLog) Append(tr io.Serializable) error {
lg.Raw = append(lg.Raw, 0) lg.Raw = append(lg.Raw, 0)
} }
b := bytes.NewBuffer(lg.Raw) if lg.buf == nil {
w := io.NewBinWriterFromIO(b) lg.buf = bytes.NewBuffer(lg.Raw)
tr.EncodeBinary(w)
if w.Err != nil {
return w.Err
} }
lg.Raw = b.Bytes() if lg.iow == nil {
lg.iow = io.NewBinWriterFromIO(lg.buf)
}
tr.EncodeBinary(lg.iow)
if lg.iow.Err != nil {
return lg.iow.Err
}
lg.Raw = lg.buf.Bytes()
lg.Raw[0]++ lg.Raw[0]++
return nil return nil
} }
// Reset resets the state of the log, clearing all entries, but keeping existing
// buffer for future writes.
func (lg *TokenTransferLog) Reset() {
lg.Raw = lg.Raw[:0]
lg.buf = nil
lg.iow = nil
}
// ForEachNEP11 iterates over a transfer log returning on the first error. // ForEachNEP11 iterates over a transfer log returning on the first error.
func (lg *TokenTransferLog) ForEachNEP11(f func(*NEP11Transfer) (bool, error)) (bool, error) { func (lg *TokenTransferLog) ForEachNEP11(f func(*NEP11Transfer) (bool, error)) (bool, error) {
if lg == nil || len(lg.Raw) == 0 { if lg == nil || len(lg.Raw) == 0 {

View file

@ -48,3 +48,12 @@ func BenchmarkScriptPushPop(t *testing.B) {
}) })
} }
} }
func BenchmarkIsSignatureContract(t *testing.B) {
b64script := "DCED2eixa9myLTNF1tTN4xvhw+HRYVMuPQzOy5Xs4utYM25BVuezJw=="
script, err := base64.StdEncoding.DecodeString(b64script)
require.NoError(t, err)
for n := 0; n < t.N; n++ {
_ = IsSignatureContract(script)
}
}

View file

@ -118,17 +118,14 @@ func ParseSignatureContract(script []byte) ([]byte, bool) {
return nil, false return nil, false
} }
ctx := NewContext(script) // We don't use Context for this simple case, it's more efficient this way.
instr, param, err := ctx.Next() if script[0] == byte(opcode.PUSHDATA1) && // PUSHDATA1
if err != nil || instr != opcode.PUSHDATA1 || len(param) != 33 { script[1] == 33 && // with a public key parameter
return nil, false script[35] == byte(opcode.SYSCALL) && // and a CheckSig SYSCALL.
binary.LittleEndian.Uint32(script[36:]) == verifyInteropID {
return script[2:35], true
} }
pub := param return nil, false
instr, param, err = ctx.Next()
if err != nil || instr != opcode.SYSCALL || binary.LittleEndian.Uint32(param) != verifyInteropID {
return nil, false
}
return pub, true
} }
// IsStandardContract checks whether the passed script is a signature or // IsStandardContract checks whether the passed script is a signature or

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