vm: optimize Next() in Context

Creating a new BinReader for every instruction is a bit too much and it adds
about 1% overhead on block import (and actually is quite visible in the VM
profiling statistics). So use a bit more ugly but efficient method.
This commit is contained in:
Roman Khimov 2019-12-13 19:31:13 +03:00
parent a9401e2ec7
commit c9257c3de4

View file

@ -1,10 +1,10 @@
package vm package vm
import ( import (
"encoding/binary"
"errors" "errors"
"github.com/CityOfZion/neo-go/pkg/crypto/hash" "github.com/CityOfZion/neo-go/pkg/crypto/hash"
"github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
"github.com/CityOfZion/neo-go/pkg/vm/opcode" "github.com/CityOfZion/neo-go/pkg/vm/opcode"
) )
@ -36,6 +36,8 @@ type Context struct {
scriptHash util.Uint160 scriptHash util.Uint160
} }
var errNoInstParam = errors.New("failed to read instruction parameter")
// NewContext returns a new Context object. // NewContext returns a new Context object.
func NewContext(b []byte) *Context { func NewContext(b []byte) *Context {
return &Context{ return &Context{
@ -49,33 +51,44 @@ func NewContext(b []byte) *Context {
// its invocation the instruction pointer points to the instruction being // its invocation the instruction pointer points to the instruction being
// returned. // returned.
func (c *Context) Next() (opcode.Opcode, []byte, error) { func (c *Context) Next() (opcode.Opcode, []byte, error) {
var err error
c.ip = c.nextip c.ip = c.nextip
if c.ip >= len(c.prog) { if c.ip >= len(c.prog) {
return opcode.RET, nil, nil return opcode.RET, nil, nil
} }
r := io.NewBinReaderFromBuf(c.prog[c.ip:])
var instrbyte = r.ReadB() var instrbyte = c.prog[c.ip]
instr := opcode.Opcode(instrbyte) instr := opcode.Opcode(instrbyte)
c.nextip++ c.nextip++
var numtoread int var numtoread int
switch instr { switch instr {
case opcode.PUSHDATA1, opcode.SYSCALL: case opcode.PUSHDATA1, opcode.SYSCALL:
var n = r.ReadB() if c.nextip >= len(c.prog) {
numtoread = int(n) err = errNoInstParam
c.nextip++ } else {
case opcode.PUSHDATA2: numtoread = int(c.prog[c.nextip])
var n = r.ReadU16LE() c.nextip++
numtoread = int(n) }
c.nextip += 2 case opcode.PUSHDATA2:
case opcode.PUSHDATA4: if c.nextip+1 >= len(c.prog) {
var n = r.ReadU32LE() err = errNoInstParam
if n > MaxItemSize { } else {
return instr, nil, errors.New("parameter is too big") numtoread = int(binary.LittleEndian.Uint16(c.prog[c.nextip : c.nextip+2]))
c.nextip += 2
}
case opcode.PUSHDATA4:
if c.nextip+3 >= len(c.prog) {
err = errNoInstParam
} else {
var n = binary.LittleEndian.Uint32(c.prog[c.nextip : c.nextip+4])
if n > MaxItemSize {
return instr, nil, errors.New("parameter is too big")
}
numtoread = int(n)
c.nextip += 4
} }
numtoread = int(n)
c.nextip += 4
case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.CALL, opcode.CALLED, opcode.CALLEDT: case opcode.JMP, opcode.JMPIF, opcode.JMPIFNOT, opcode.CALL, opcode.CALLED, opcode.CALLEDT:
numtoread = 2 numtoread = 2
case opcode.CALLI: case opcode.CALLI:
@ -92,11 +105,14 @@ func (c *Context) Next() (opcode.Opcode, []byte, error) {
return instr, nil, nil return instr, nil, nil
} }
} }
parameter := make([]byte, numtoread) if c.nextip+numtoread-1 >= len(c.prog) {
r.ReadBytes(parameter) err = errNoInstParam
if r.Err != nil {
return instr, nil, errors.New("failed to read instruction parameter")
} }
if err != nil {
return instr, nil, err
}
parameter := make([]byte, numtoread)
copy(parameter, c.prog[c.nextip:c.nextip+numtoread])
c.nextip += numtoread c.nextip += numtoread
return instr, parameter, nil return instr, parameter, nil
} }