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 {
var result int64
for _, op := range opcodes {
result += coefficients[op]
result += int64(coefficients[op])
}
return result * base
}
var coefficients = map[opcode.Opcode]int64{
var coefficients = [256]uint16{
opcode.PUSHINT8: 1 << 0,
opcode.PUSHINT16: 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
}
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)
if err != nil {
return err
}
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 {
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 {
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")
}
h, err := getStandbyValidatorsHash(ic)

View file

@ -189,7 +189,8 @@ func (n *NEO) Initialize(ic *interop.Context) error {
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")
}
@ -391,12 +392,13 @@ func (n *NEO) getGASPerVote(d dao.DAO, key []byte, index ...uint32) []big.Int {
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)
if err != nil {
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")
}
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 / totalSupply must be >= 0.2
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()
count := len(sbVals)

View file

@ -33,7 +33,7 @@ type nep17TokenNative struct {
symbol string
decimals 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)
}
@ -95,19 +95,21 @@ func (c *nep17TokenNative) Decimals(_ *interop.Context, _ []stackitem.Item) stac
}
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)
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 {
return d.PutStorageItem(c.ID, totalSupplyKey, bigint.ToBytes(supply))
func (c *nep17TokenNative) saveTotalSupply(d dao.DAO, si state.StorageItem, supply *big.Int) error {
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 {
@ -173,29 +175,11 @@ func (c *nep17TokenNative) updateAccBalance(ic *interop.Context, acc util.Uint16
return errors.New("insufficient funds")
}
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 si != nil && amount.Sign() < 0 {
if si != nil && amount.Sign() <= 0 {
_ = ic.DAO.PutStorageItem(c.ID, key, si)
}
return err
@ -288,7 +272,7 @@ func (c *nep17TokenNative) addTokens(ic *interop.Context, h util.Uint160, amount
if si == nil {
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)
}
var err error
@ -301,9 +285,9 @@ func (c *nep17TokenNative) addTokens(ic *interop.Context, h util.Uint160, amount
panic(err)
}
supply := c.getTotalSupply(ic.DAO)
buf, supply := c.getTotalSupply(ic.DAO)
supply.Add(supply, amount)
err = c.saveTotalSupply(ic.DAO, supply)
err = c.saveTotalSupply(ic.DAO, buf, supply)
if err != nil {
panic(err)
}

View file

@ -91,15 +91,18 @@ func (s *NEOBalance) Bytes() []byte {
// ToStackItem implements stackitem.Convertible interface. It never returns an error.
func (s *NEOBalance) ToStackItem() (stackitem.Item, error) {
resItem, _ := s.NEP17Balance.ToStackItem()
result := resItem.(*stackitem.Struct)
result.Append(stackitem.NewBigInteger(big.NewInt(int64(s.BalanceHeight))))
var voteItem stackitem.Item
if s.VoteTo != nil {
result.Append(stackitem.NewByteArray(s.VoteTo.Bytes()))
voteItem = stackitem.NewByteArray(s.VoteTo.Bytes())
} 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.

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
)
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).
func IsValid(op Opcode) bool {
_, ok := _Opcode_map[op] // We rely on stringer here, it has a map anyway.
return ok
return validCodes[int(op)]
}

View file

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

View file

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

View file

@ -99,7 +99,7 @@ func New() *VM {
func NewWithTrigger(t trigger.Type) *VM {
vm := &VM{
state: NoneState,
istack: NewStack("invocation"),
istack: newStack("invocation", nil),
refs: newRefCounter(),
trigger: t,
@ -107,17 +107,10 @@ func NewWithTrigger(t trigger.Type) *VM {
Invocations: make(map[util.Uint160]int),
}
vm.estack = vm.newItemStack("evaluation")
vm.estack = newStack("evaluation", vm.refs)
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.
// f accepts vm's Context, current instruction and instruction parameter.
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) {
v.checkInvocationStackSize()
ctx := NewContextWithParams(b, 0, -1, 0)
v.estack = v.newItemStack("estack")
v.estack = newStack("evaluation", v.refs)
ctx.estack = v.estack
ctx.tryStack = NewStack("exception")
ctx.tryStack = newStack("exception", nil)
ctx.callFlag = f
ctx.static = newSlot(v.refs)
ctx.callingScriptHash = v.GetCurrentScriptHash()
@ -1527,7 +1520,7 @@ func (v *VM) call(ctx *Context, offset int) {
newCtx.RetCount = -1
newCtx.local = nil
newCtx.arguments = nil
newCtx.tryStack = NewStack("exception")
newCtx.tryStack = newStack("exception", nil)
newCtx.NEF = ctx.NEF
v.istack.PushVal(newCtx)
v.Jump(newCtx, offset)