Merge pull request #2106 from nspcc-dev/microopt

Microoptimizations
This commit is contained in:
Roman Khimov 2021-08-03 21:28:35 +03:00 committed by GitHub
commit 5e18a6141e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 100 additions and 61 deletions

View file

@ -8,12 +8,12 @@ import (
func Opcode(base int64, opcodes ...opcode.Opcode) int64 { func Opcode(base int64, opcodes ...opcode.Opcode) int64 {
var result int64 var result int64
for _, op := range opcodes { for _, op := range opcodes {
result += coefficients[op] result += int64(coefficients[op])
} }
return result * base return result * base
} }
var coefficients = map[opcode.Opcode]int64{ var coefficients = [256]uint16{
opcode.PUSHINT8: 1 << 0, opcode.PUSHINT8: 1 << 0,
opcode.PUSHINT16: 1 << 0, opcode.PUSHINT16: 1 << 0,
opcode.PUSHINT32: 1 << 0, opcode.PUSHINT32: 1 << 0,

View file

@ -0,0 +1,19 @@
package fee
import (
"testing"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
)
const feeFactor = 30
// The most common Opcode() use case is to get price for single opcode.
func BenchmarkOpcode1(t *testing.B) {
// Just so that we don't always test the same opcode.
script := []opcode.Opcode{opcode.NOP, opcode.ADD, opcode.SYSCALL, opcode.APPEND}
l := len(script)
for n := 0; n < t.N; n++ {
_ = Opcode(feeFactor, script[n%l])
}
}

View file

@ -54,13 +54,17 @@ func newGAS(init int64) *GAS {
return g return g
} }
func (g *GAS) increaseBalance(_ *interop.Context, _ util.Uint160, si *state.StorageItem, amount *big.Int) error { func (g *GAS) increaseBalance(_ *interop.Context, _ util.Uint160, si *state.StorageItem, amount *big.Int, checkBal *big.Int) error {
acc, err := state.NEP17BalanceFromBytes(*si) acc, err := state.NEP17BalanceFromBytes(*si)
if err != nil { if err != nil {
return err return err
} }
if sign := amount.Sign(); sign == 0 { if sign := amount.Sign(); sign == 0 {
return nil // Requested self-transfer amount can be higher than actual balance.
if checkBal != nil && acc.Balance.Cmp(checkBal) < 0 {
err = errors.New("insufficient funds")
}
return err
} else if sign == -1 && acc.Balance.Cmp(new(big.Int).Neg(amount)) == -1 { } else if sign == -1 && acc.Balance.Cmp(new(big.Int).Neg(amount)) == -1 {
return errors.New("insufficient funds") return errors.New("insufficient funds")
} }
@ -104,7 +108,8 @@ func (g *GAS) Initialize(ic *interop.Context) error {
if err := g.nep17TokenNative.Initialize(ic); err != nil { if err := g.nep17TokenNative.Initialize(ic); err != nil {
return err return err
} }
if g.nep17TokenNative.getTotalSupply(ic.DAO).Sign() != 0 { _, totalSupply := g.nep17TokenNative.getTotalSupply(ic.DAO)
if totalSupply.Sign() != 0 {
return errors.New("already initialized") return errors.New("already initialized")
} }
h, err := getStandbyValidatorsHash(ic) h, err := getStandbyValidatorsHash(ic)

View file

@ -189,7 +189,8 @@ func (n *NEO) Initialize(ic *interop.Context) error {
return err return err
} }
if n.nep17TokenNative.getTotalSupply(ic.DAO).Sign() != 0 { _, totalSupply := n.nep17TokenNative.getTotalSupply(ic.DAO)
if totalSupply.Sign() != 0 {
return errors.New("already initialized") return errors.New("already initialized")
} }
@ -391,12 +392,13 @@ func (n *NEO) getGASPerVote(d dao.DAO, key []byte, index ...uint32) []big.Int {
return reward return reward
} }
func (n *NEO) increaseBalance(ic *interop.Context, h util.Uint160, si *state.StorageItem, amount *big.Int) error { func (n *NEO) increaseBalance(ic *interop.Context, h util.Uint160, si *state.StorageItem, amount *big.Int, checkBal *big.Int) error {
acc, err := state.NEOBalanceFromBytes(*si) acc, err := state.NEOBalanceFromBytes(*si)
if err != nil { if err != nil {
return err return err
} }
if amount.Sign() == -1 && acc.Balance.Cmp(new(big.Int).Neg(amount)) == -1 { if (amount.Sign() == -1 && acc.Balance.Cmp(new(big.Int).Neg(amount)) == -1) ||
(amount.Sign() == 0 && checkBal != nil && acc.Balance.Cmp(checkBal) == -1) {
return errors.New("insufficient funds") return errors.New("insufficient funds")
} }
if err := n.distributeGas(ic, h, acc); err != nil { if err := n.distributeGas(ic, h, acc); err != nil {
@ -976,7 +978,8 @@ func (n *NEO) computeCommitteeMembers(bc blockchainer.Blockchainer, d dao.DAO) (
votersCount := bigint.FromBytes(si) votersCount := bigint.FromBytes(si)
// votersCount / totalSupply must be >= 0.2 // votersCount / totalSupply must be >= 0.2
votersCount.Mul(votersCount, big.NewInt(effectiveVoterTurnout)) votersCount.Mul(votersCount, big.NewInt(effectiveVoterTurnout))
voterTurnout := votersCount.Div(votersCount, n.getTotalSupply(d)) _, totalSupply := n.getTotalSupply(d)
voterTurnout := votersCount.Div(votersCount, totalSupply)
sbVals := bc.GetStandByCommittee() sbVals := bc.GetStandByCommittee()
count := len(sbVals) count := len(sbVals)

View file

@ -33,7 +33,7 @@ type nep17TokenNative struct {
symbol string symbol string
decimals int64 decimals int64
factor int64 factor int64
incBalance func(*interop.Context, util.Uint160, *state.StorageItem, *big.Int) error incBalance func(*interop.Context, util.Uint160, *state.StorageItem, *big.Int, *big.Int) error
balFromBytes func(item *state.StorageItem) (*big.Int, error) balFromBytes func(item *state.StorageItem) (*big.Int, error)
} }
@ -95,19 +95,21 @@ func (c *nep17TokenNative) Decimals(_ *interop.Context, _ []stackitem.Item) stac
} }
func (c *nep17TokenNative) TotalSupply(ic *interop.Context, _ []stackitem.Item) stackitem.Item { func (c *nep17TokenNative) TotalSupply(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
return stackitem.NewBigInteger(c.getTotalSupply(ic.DAO)) _, supply := c.getTotalSupply(ic.DAO)
return stackitem.NewBigInteger(supply)
} }
func (c *nep17TokenNative) getTotalSupply(d dao.DAO) *big.Int { func (c *nep17TokenNative) getTotalSupply(d dao.DAO) (state.StorageItem, *big.Int) {
si := d.GetStorageItem(c.ID, totalSupplyKey) si := d.GetStorageItem(c.ID, totalSupplyKey)
if si == nil { if si == nil {
return big.NewInt(0) si = []byte{}
} }
return bigint.FromBytes(si) return si, bigint.FromBytes(si)
} }
func (c *nep17TokenNative) saveTotalSupply(d dao.DAO, supply *big.Int) error { func (c *nep17TokenNative) saveTotalSupply(d dao.DAO, si state.StorageItem, supply *big.Int) error {
return d.PutStorageItem(c.ID, totalSupplyKey, bigint.ToBytes(supply)) si = state.StorageItem(bigint.ToPreallocatedBytes(supply, si))
return d.PutStorageItem(c.ID, totalSupplyKey, si)
} }
func (c *nep17TokenNative) Transfer(ic *interop.Context, args []stackitem.Item) stackitem.Item { func (c *nep17TokenNative) Transfer(ic *interop.Context, args []stackitem.Item) stackitem.Item {
@ -173,29 +175,11 @@ func (c *nep17TokenNative) updateAccBalance(ic *interop.Context, acc util.Uint16
return errors.New("insufficient funds") return errors.New("insufficient funds")
} }
si = state.StorageItem{} si = state.StorageItem{}
} else if amount.Sign() == 0 && requiredBalance != nil {
// If amount == 0 then it's either a round trip or an empty transfer. In
// case of a round trip account's balance may still be less than actual
// transfer's amount, so we need to check it. Other cases are handled by
// `incBalance` method.
balance, err := c.balFromBytes(&si)
if err != nil {
return fmt.Errorf("failed to deserialise balance: %w", err)
}
if balance.Cmp(requiredBalance) < 0 {
// Firstly, need to put it back to storage as it affects dumps.
err = ic.DAO.PutStorageItem(c.ID, key, si)
if err != nil {
return err
}
// Finally, return an error.
return errors.New("insufficient funds")
}
} }
err := c.incBalance(ic, acc, &si, amount) err := c.incBalance(ic, acc, &si, amount, requiredBalance)
if err != nil { if err != nil {
if si != nil && amount.Sign() < 0 { if si != nil && amount.Sign() <= 0 {
_ = ic.DAO.PutStorageItem(c.ID, key, si) _ = ic.DAO.PutStorageItem(c.ID, key, si)
} }
return err return err
@ -288,7 +272,7 @@ func (c *nep17TokenNative) addTokens(ic *interop.Context, h util.Uint160, amount
if si == nil { if si == nil {
si = state.StorageItem{} si = state.StorageItem{}
} }
if err := c.incBalance(ic, h, &si, amount); err != nil { if err := c.incBalance(ic, h, &si, amount, nil); err != nil {
panic(err) panic(err)
} }
var err error var err error
@ -301,9 +285,9 @@ func (c *nep17TokenNative) addTokens(ic *interop.Context, h util.Uint160, amount
panic(err) panic(err)
} }
supply := c.getTotalSupply(ic.DAO) buf, supply := c.getTotalSupply(ic.DAO)
supply.Add(supply, amount) supply.Add(supply, amount)
err = c.saveTotalSupply(ic.DAO, supply) err = c.saveTotalSupply(ic.DAO, buf, supply)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View file

@ -91,15 +91,18 @@ func (s *NEOBalance) Bytes() []byte {
// ToStackItem implements stackitem.Convertible interface. It never returns an error. // ToStackItem implements stackitem.Convertible interface. It never returns an error.
func (s *NEOBalance) ToStackItem() (stackitem.Item, error) { func (s *NEOBalance) ToStackItem() (stackitem.Item, error) {
resItem, _ := s.NEP17Balance.ToStackItem() var voteItem stackitem.Item
result := resItem.(*stackitem.Struct)
result.Append(stackitem.NewBigInteger(big.NewInt(int64(s.BalanceHeight))))
if s.VoteTo != nil { if s.VoteTo != nil {
result.Append(stackitem.NewByteArray(s.VoteTo.Bytes())) voteItem = stackitem.NewByteArray(s.VoteTo.Bytes())
} else { } else {
result.Append(stackitem.Null{}) voteItem = stackitem.Null{}
} }
return result, nil return stackitem.NewStruct([]stackitem.Item{
stackitem.NewBigInteger(&s.Balance),
stackitem.NewBigInteger(big.NewInt(int64(s.BalanceHeight))),
voteItem,
}), nil
} }
// FromStackItem converts stackitem.Item to NEOBalance. // FromStackItem converts stackitem.Item to NEOBalance.

View file

@ -0,0 +1,15 @@
package opcode
import (
"testing"
)
// IsValid() is called for every VM instruction.
func BenchmarkIsValid(t *testing.B) {
// Just so that we don't always test the same opcode.
script := []Opcode{NOP, ADD, SYSCALL, APPEND, 0xff, 0xf0}
l := len(script)
for n := 0; n < t.N; n++ {
_ = IsValid(script[n%l])
}
}

View file

@ -222,8 +222,16 @@ const (
CONVERT Opcode = 0xDB CONVERT Opcode = 0xDB
) )
var validCodes [256]bool
func init() {
// We rely on stringer here, it has a map anyway.
for op := range _Opcode_map {
validCodes[int(op)] = true
}
}
// IsValid returns true if the opcode passed is valid (defined in the VM). // IsValid returns true if the opcode passed is valid (defined in the VM).
func IsValid(op Opcode) bool { func IsValid(op Opcode) bool {
_, ok := _Opcode_map[op] // We rely on stringer here, it has a map anyway. return validCodes[int(op)]
return ok
} }

View file

@ -18,6 +18,9 @@ func newRefCounter() *refCounter {
// Add adds an item to the reference counter. // Add adds an item to the reference counter.
func (r *refCounter) Add(item stackitem.Item) { func (r *refCounter) Add(item stackitem.Item) {
if r == nil {
return
}
r.size++ r.size++
switch item.(type) { switch item.(type) {
@ -41,6 +44,9 @@ func (r *refCounter) Add(item stackitem.Item) {
// Remove removes item from the reference counter. // Remove removes item from the reference counter.
func (r *refCounter) Remove(item stackitem.Item) { func (r *refCounter) Remove(item stackitem.Item) {
if r == nil {
return
}
r.size-- r.size--
switch item.(type) { switch item.(type) {

View file

@ -158,13 +158,16 @@ type Stack struct {
// NewStack returns a new stack name by the given name. // NewStack returns a new stack name by the given name.
func NewStack(n string) *Stack { func NewStack(n string) *Stack {
return newStack(n, newRefCounter())
}
func newStack(n string, refc *refCounter) *Stack {
s := &Stack{ s := &Stack{
name: n, name: n,
refs: refc,
} }
s.top.next = &s.top s.top.next = &s.top
s.top.prev = &s.top s.top.prev = &s.top
s.len = 0
s.refs = newRefCounter()
return s return s
} }

View file

@ -99,7 +99,7 @@ func New() *VM {
func NewWithTrigger(t trigger.Type) *VM { func NewWithTrigger(t trigger.Type) *VM {
vm := &VM{ vm := &VM{
state: NoneState, state: NoneState,
istack: NewStack("invocation"), istack: newStack("invocation", nil),
refs: newRefCounter(), refs: newRefCounter(),
trigger: t, trigger: t,
@ -107,17 +107,10 @@ func NewWithTrigger(t trigger.Type) *VM {
Invocations: make(map[util.Uint160]int), Invocations: make(map[util.Uint160]int),
} }
vm.estack = vm.newItemStack("evaluation") vm.estack = newStack("evaluation", vm.refs)
return vm return vm
} }
func (v *VM) newItemStack(n string) *Stack {
s := NewStack(n)
s.refs = v.refs
return s
}
// SetPriceGetter registers the given PriceGetterFunc in v. // SetPriceGetter registers the given PriceGetterFunc in v.
// f accepts vm's Context, current instruction and instruction parameter. // f accepts vm's Context, current instruction and instruction parameter.
func (v *VM) SetPriceGetter(f func(opcode.Opcode, []byte) int64) { func (v *VM) SetPriceGetter(f func(opcode.Opcode, []byte) int64) {
@ -288,9 +281,9 @@ func (v *VM) LoadScript(b []byte) {
func (v *VM) LoadScriptWithFlags(b []byte, f callflag.CallFlag) { func (v *VM) LoadScriptWithFlags(b []byte, f callflag.CallFlag) {
v.checkInvocationStackSize() v.checkInvocationStackSize()
ctx := NewContextWithParams(b, 0, -1, 0) ctx := NewContextWithParams(b, 0, -1, 0)
v.estack = v.newItemStack("estack") v.estack = newStack("evaluation", v.refs)
ctx.estack = v.estack ctx.estack = v.estack
ctx.tryStack = NewStack("exception") ctx.tryStack = newStack("exception", nil)
ctx.callFlag = f ctx.callFlag = f
ctx.static = newSlot(v.refs) ctx.static = newSlot(v.refs)
ctx.callingScriptHash = v.GetCurrentScriptHash() ctx.callingScriptHash = v.GetCurrentScriptHash()
@ -1527,7 +1520,7 @@ func (v *VM) call(ctx *Context, offset int) {
newCtx.RetCount = -1 newCtx.RetCount = -1
newCtx.local = nil newCtx.local = nil
newCtx.arguments = nil newCtx.arguments = nil
newCtx.tryStack = NewStack("exception") newCtx.tryStack = newStack("exception", nil)
newCtx.NEF = ctx.NEF newCtx.NEF = ctx.NEF
v.istack.PushVal(newCtx) v.istack.PushVal(newCtx)
v.Jump(newCtx, offset) v.Jump(newCtx, offset)