vm: simplify slot and make it private

Hiding refcounter inside a slot is actually a good idea, but it makes the
structure somewhat bigger, especially given that the refcounter is the same
and belongs more to VM or Context. New structure is a bit more efficient:

name                    old time/op    new time/op    delta
ScriptFibonacci-8          672µs ± 2%     644µs ± 0%  -4.15%  (p=0.008 n=5+5)
ScriptNestedRefCount-8    1.08ms ± 1%    1.05ms ± 2%  -2.56%  (p=0.008 n=5+5)
ScriptPushPop/4-8         1.52µs ± 1%    1.47µs ± 1%  -3.14%  (p=0.008 n=5+5)
ScriptPushPop/16-8        3.66µs ± 1%    3.54µs ± 1%  -3.24%  (p=0.008 n=5+5)
ScriptPushPop/128-8       24.7µs ± 1%    23.2µs ± 1%  -6.14%  (p=0.008 n=5+5)
ScriptPushPop/1024-8       183µs ± 1%     173µs ± 1%  -5.01%  (p=0.008 n=5+5)

name                    old alloc/op   new alloc/op   delta
ScriptFibonacci-8          114kB ± 0%     114kB ± 0%    ~     (p=0.079 n=4+5)
ScriptNestedRefCount-8     241kB ± 0%     241kB ± 0%    ~     (p=0.333 n=5+4)
ScriptPushPop/4-8           160B ± 0%      160B ± 0%    ~     (all equal)
ScriptPushPop/16-8          640B ± 0%      640B ± 0%    ~     (all equal)
ScriptPushPop/128-8       8.70kB ± 0%    8.70kB ± 0%    ~     (all equal)
ScriptPushPop/1024-8      73.2kB ± 0%    73.2kB ± 0%    ~     (all equal)

name                    old allocs/op  new allocs/op  delta
ScriptFibonacci-8          3.17k ± 0%     3.17k ± 0%  -0.03%  (p=0.008 n=5+5)
ScriptNestedRefCount-8     10.7k ± 0%     10.7k ± 0%    ~     (all equal)
ScriptPushPop/4-8           8.00 ± 0%      8.00 ± 0%    ~     (all equal)
ScriptPushPop/16-8          32.0 ± 0%      32.0 ± 0%    ~     (all equal)
ScriptPushPop/128-8          259 ± 0%       259 ± 0%    ~     (all equal)
ScriptPushPop/1024-8       2.05k ± 0%     2.05k ± 0%    ~     (all equal)

It'd be especially nice to internalize static slot, but as we can't compare
slices it's not possible.
This commit is contained in:
Roman Khimov 2021-11-30 15:03:01 +03:00
parent a0473aca92
commit ee05f73b6f
6 changed files with 54 additions and 66 deletions

View file

@ -32,9 +32,9 @@ type Context struct {
// Evaluation stack pointer.
estack *Stack
static *Slot
local *Slot
arguments *Slot
static *slot
local slot
arguments slot
// Exception context stack.
tryStack Stack
@ -277,16 +277,19 @@ func (c *Context) DumpStaticSlot() string {
// DumpLocalSlot returns json formatted representation of the given slot.
func (c *Context) DumpLocalSlot() string {
return dumpSlot(c.local)
return dumpSlot(&c.local)
}
// DumpArgumentsSlot returns json formatted representation of the given slot.
func (c *Context) DumpArgumentsSlot() string {
return dumpSlot(c.arguments)
return dumpSlot(&c.arguments)
}
// dumpSlot returns json formatted representation of the given slot.
func dumpSlot(s *Slot) string {
func dumpSlot(s *slot) string {
if s == nil || *s == nil {
return "[]"
}
b, _ := json.MarshalIndent(s, "", " ")
return string(b)
}

View file

@ -239,8 +239,8 @@ func compareStacks(t *testing.T, expected []vmUTStackItem, actual *Stack) {
compareItemArrays(t, expected, actual.Len(), func(i int) stackitem.Item { return actual.Peek(i).Item() })
}
func compareSlots(t *testing.T, expected []vmUTStackItem, actual *Slot) {
if actual.storage == nil && len(expected) == 0 {
func compareSlots(t *testing.T, expected []vmUTStackItem, actual *slot) {
if (actual == nil || *actual == nil) && len(expected) == 0 {
return
}
require.NotNil(t, actual)

View file

@ -59,8 +59,8 @@ func opParamSlotsPushVM(op opcode.Opcode, param []byte, sslot int, slotloc int,
v.Context().static.init(sslot)
}
if slotloc != 0 && slotarg != 0 {
v.Context().local = v.newSlot(slotloc)
v.Context().arguments = v.newSlot(slotarg)
v.Context().local.init(slotloc)
v.Context().arguments.init(slotarg)
}
for i := range items {
item, ok := items[i].(stackitem.Item)

View file

@ -6,75 +6,58 @@ import (
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
// Slot is a fixed-size slice of stack items.
type Slot struct {
storage []stackitem.Item
refs *refCounter
}
// newSlot returns new slot with the provided reference counter.
func newSlot(refs *refCounter) *Slot {
return &Slot{
refs: refs,
}
}
// slot is a fixed-size slice of stack items.
type slot []stackitem.Item
// init sets static slot size to n. It is intended to be used only by INITSSLOT.
func (s *Slot) init(n int) {
if s.storage != nil {
func (s *slot) init(n int) {
if *s != nil {
panic("already initialized")
}
s.storage = make([]stackitem.Item, n)
}
func (v *VM) newSlot(n int) *Slot {
s := newSlot(&v.refs)
s.init(n)
return s
*s = make([]stackitem.Item, n)
}
// Set sets i-th storage slot.
func (s *Slot) Set(i int, item stackitem.Item) {
if s.storage[i] == item {
func (s slot) Set(i int, item stackitem.Item, refs *refCounter) {
if s[i] == item {
return
}
old := s.storage[i]
s.storage[i] = item
old := s[i]
s[i] = item
if old != nil {
s.refs.Remove(old)
refs.Remove(old)
}
s.refs.Add(item)
refs.Add(item)
}
// Get returns item contained in i-th slot.
func (s *Slot) Get(i int) stackitem.Item {
if item := s.storage[i]; item != nil {
func (s slot) Get(i int) stackitem.Item {
if item := s[i]; item != nil {
return item
}
return stackitem.Null{}
}
// Clear removes all slot variables from reference counter.
func (s *Slot) Clear() {
for _, item := range s.storage {
s.refs.Remove(item)
func (s slot) Clear(refs *refCounter) {
for _, item := range s {
refs.Remove(item)
}
}
// Size returns slot size.
func (s *Slot) Size() int {
if s.storage == nil {
func (s slot) Size() int {
if s == nil {
panic("not initialized")
}
return len(s.storage)
return len(s)
}
// MarshalJSON implements JSON marshalling interface.
func (s *Slot) MarshalJSON() ([]byte, error) {
items := s.storage
arr := make([]json.RawMessage, len(items))
for i := range items {
data, err := stackitem.ToJSONWithTypes(items[i])
func (s slot) MarshalJSON() ([]byte, error) {
arr := make([]json.RawMessage, len(s))
for i := range s {
data, err := stackitem.ToJSONWithTypes(s[i])
if err == nil {
arr[i] = data
}

View file

@ -9,8 +9,8 @@ import (
)
func TestSlot_Get(t *testing.T) {
s := newSlot(newRefCounter())
require.NotNil(t, s)
rc := newRefCounter()
var s slot
require.Panics(t, func() { s.Size() })
s.init(3)
@ -20,6 +20,6 @@ func TestSlot_Get(t *testing.T) {
item := s.Get(2)
require.Equal(t, stackitem.Null{}, item)
s.Set(1, stackitem.NewBigInteger(big.NewInt(42)))
s.Set(1, stackitem.NewBigInteger(big.NewInt(42)), rc)
require.Equal(t, stackitem.NewBigInteger(big.NewInt(42)), s.Get(1))
}

View file

@ -310,13 +310,15 @@ func (v *VM) LoadNEFMethod(exe *nef.File, caller util.Uint160, hash util.Uint160
// It should be used for calling from native contracts.
func (v *VM) loadScriptWithCallingHash(b []byte, exe *nef.File, caller util.Uint160,
hash util.Uint160, f callflag.CallFlag, rvcount int, offset int) {
var sl slot
v.checkInvocationStackSize()
ctx := NewContextWithParams(b, rvcount, offset)
v.estack = newStack("evaluation", &v.refs)
ctx.estack = v.estack
initStack(&ctx.tryStack, "exception", nil)
ctx.callFlag = f
ctx.static = newSlot(&v.refs)
ctx.static = &sl
ctx.scriptHash = hash
ctx.callingScriptHash = caller
ctx.NEF = exe
@ -615,13 +617,13 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
panic("zero argument")
}
if parameter[0] > 0 {
ctx.local = v.newSlot(int(parameter[0]))
ctx.local.init(int(parameter[0]))
}
if parameter[1] > 0 {
sz := int(parameter[1])
ctx.arguments = v.newSlot(sz)
ctx.arguments.init(sz)
for i := 0; i < sz; i++ {
ctx.arguments.Set(i, v.estack.Pop().Item())
ctx.arguments.Set(i, v.estack.Pop().Item(), &v.refs)
}
}
@ -635,11 +637,11 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
case opcode.STSFLD0, opcode.STSFLD1, opcode.STSFLD2, opcode.STSFLD3, opcode.STSFLD4, opcode.STSFLD5, opcode.STSFLD6:
item := v.estack.Pop().Item()
ctx.static.Set(int(op-opcode.STSFLD0), item)
ctx.static.Set(int(op-opcode.STSFLD0), item, &v.refs)
case opcode.STSFLD:
item := v.estack.Pop().Item()
ctx.static.Set(int(parameter[0]), item)
ctx.static.Set(int(parameter[0]), item, &v.refs)
case opcode.LDLOC0, opcode.LDLOC1, opcode.LDLOC2, opcode.LDLOC3, opcode.LDLOC4, opcode.LDLOC5, opcode.LDLOC6:
item := ctx.local.Get(int(op - opcode.LDLOC0))
@ -651,11 +653,11 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
case opcode.STLOC0, opcode.STLOC1, opcode.STLOC2, opcode.STLOC3, opcode.STLOC4, opcode.STLOC5, opcode.STLOC6:
item := v.estack.Pop().Item()
ctx.local.Set(int(op-opcode.STLOC0), item)
ctx.local.Set(int(op-opcode.STLOC0), item, &v.refs)
case opcode.STLOC:
item := v.estack.Pop().Item()
ctx.local.Set(int(parameter[0]), item)
ctx.local.Set(int(parameter[0]), item, &v.refs)
case opcode.LDARG0, opcode.LDARG1, opcode.LDARG2, opcode.LDARG3, opcode.LDARG4, opcode.LDARG5, opcode.LDARG6:
item := ctx.arguments.Get(int(op - opcode.LDARG0))
@ -667,11 +669,11 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
case opcode.STARG0, opcode.STARG1, opcode.STARG2, opcode.STARG3, opcode.STARG4, opcode.STARG5, opcode.STARG6:
item := v.estack.Pop().Item()
ctx.arguments.Set(int(op-opcode.STARG0), item)
ctx.arguments.Set(int(op-opcode.STARG0), item, &v.refs)
case opcode.STARG:
item := v.estack.Pop().Item()
ctx.arguments.Set(int(parameter[0]), item)
ctx.arguments.Set(int(parameter[0]), item, &v.refs)
case opcode.NEWBUFFER:
n := toInt(v.estack.Pop().BigInt())
@ -1527,14 +1529,14 @@ func (v *VM) execute(ctx *Context, op opcode.Opcode, parameter []byte) (err erro
func (v *VM) unloadContext(ctx *Context) {
if ctx.local != nil {
ctx.local.Clear()
ctx.local.Clear(&v.refs)
}
if ctx.arguments != nil {
ctx.arguments.Clear()
ctx.arguments.Clear(&v.refs)
}
currCtx := v.Context()
if ctx.static != nil && currCtx != nil && ctx.static != currCtx.static {
ctx.static.Clear()
ctx.static.Clear(&v.refs)
}
}