From 59fbc689e4cc9515ea758a65ad1c8230194ed7be Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Tue, 23 Aug 2022 09:34:00 +0300 Subject: [PATCH 1/2] vm/bench: extend refcounter benchmarks Adding an array multiple times leads to the fast update via `IncRC`. This hides the allocation that is there on the first addition. In this commit add another benchmark which measures Add/Remove together, to ensure that `switch` in `refCounter.Add` is entered. Benchmark results are meaningful, because `Add`/`Remove` have almost identical implementation. Signed-off-by: Evgeniy Stratonikov --- pkg/vm/ref_counter_test.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pkg/vm/ref_counter_test.go b/pkg/vm/ref_counter_test.go index f0e63b318..8da020671 100644 --- a/pkg/vm/ref_counter_test.go +++ b/pkg/vm/ref_counter_test.go @@ -55,3 +55,14 @@ func BenchmarkRefCounter_Add(b *testing.B) { rc.Add(a) } } + +func BenchmarkRefCounter_AddRemove(b *testing.B) { + a := stackitem.NewArray([]stackitem.Item{}) + rc := newRefCounter() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + rc.Add(a) + rc.Remove(a) + } +} From 9712be78fd01d056bcb1ea7474238ebc23f30a03 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Tue, 23 Aug 2022 09:19:09 +0300 Subject: [PATCH 2/2] vm: optimize refcounter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` name old time/op new time/op delta RefCounter_Add-8 9.47ns ± 4% 2.75ns ± 1% -70.92% (p=0.000 n=10+9) RefCounter_AddRemove-8 88.9ns ±20% 5.5ns ± 4% -93.78% (p=0.000 n=10+10) name old alloc/op new alloc/op delta RefCounter_Add-8 0.00B 0.00B ~ (all equal) RefCounter_AddRemove-8 48.0B ± 0% 0.0B -100.00% (p=0.000 n=10+10) name old allocs/op new allocs/op delta RefCounter_Add-8 0.00 0.00 ~ (all equal) RefCounter_AddRemove-8 2.00 ± 0% 0.00 -100.00% (p=0.000 n=10+10) ``` Signed-off-by: Evgeniy Stratonikov --- pkg/vm/fuzz_test.go | 6 ++++ pkg/vm/ref_counter.go | 65 ++++++++++++++++++++++--------------------- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/pkg/vm/fuzz_test.go b/pkg/vm/fuzz_test.go index fc9434f93..93c789e1a 100644 --- a/pkg/vm/fuzz_test.go +++ b/pkg/vm/fuzz_test.go @@ -16,6 +16,12 @@ var fuzzSeedValidScripts = [][]byte{ makeProgram(opcode.PUSH2, opcode.NEWARRAY, opcode.DUP, opcode.PUSH0, opcode.PUSH1, opcode.SETITEM, opcode.VALUES), append([]byte{byte(opcode.PUSHDATA1), 10}, randomBytes(10)...), append([]byte{byte(opcode.PUSHDATA1), 100}, randomBytes(100)...), + // Simplified version of fuzzer output from #2659. + {byte(opcode.CALL), 3, byte(opcode.ASSERT), + byte(opcode.CALL), 3, byte(opcode.ASSERT), + byte(opcode.DEPTH), byte(opcode.PACKSTRUCT), byte(opcode.DUP), + byte(opcode.UNPACK), byte(opcode.PACKSTRUCT), byte(opcode.POPITEM), + byte(opcode.DEPTH)}, } func FuzzIsScriptCorrect(f *testing.F) { diff --git a/pkg/vm/ref_counter.go b/pkg/vm/ref_counter.go index a493a43ce..f3e1ab42d 100644 --- a/pkg/vm/ref_counter.go +++ b/pkg/vm/ref_counter.go @@ -7,15 +7,6 @@ import ( // refCounter represents a reference counter for the VM. type refCounter int -type ( - rcInc interface { - IncRC() int - } - rcDec interface { - DecRC() int - } -) - func newRefCounter() *refCounter { return new(refCounter) } @@ -27,20 +18,26 @@ func (r *refCounter) Add(item stackitem.Item) { } *r++ - irc, ok := item.(rcInc) - if !ok || irc.IncRC() > 1 { - return - } switch t := item.(type) { - case *stackitem.Array, *stackitem.Struct: - for _, it := range item.Value().([]stackitem.Item) { - r.Add(it) + case *stackitem.Array: + if t.IncRC() == 1 { + for _, it := range t.Value().([]stackitem.Item) { + r.Add(it) + } + } + case *stackitem.Struct: + if t.IncRC() == 1 { + for _, it := range t.Value().([]stackitem.Item) { + r.Add(it) + } } case *stackitem.Map: - elems := t.Value().([]stackitem.MapElement) - for i := range elems { - r.Add(elems[i].Key) - r.Add(elems[i].Value) + if t.IncRC() == 1 { + elems := t.Value().([]stackitem.MapElement) + for i := range elems { + r.Add(elems[i].Key) + r.Add(elems[i].Value) + } } } } @@ -52,20 +49,26 @@ func (r *refCounter) Remove(item stackitem.Item) { } *r-- - irc, ok := item.(rcDec) - if !ok || irc.DecRC() > 0 { - return - } switch t := item.(type) { - case *stackitem.Array, *stackitem.Struct: - for _, it := range item.Value().([]stackitem.Item) { - r.Remove(it) + case *stackitem.Array: + if t.DecRC() == 0 { + for _, it := range t.Value().([]stackitem.Item) { + r.Remove(it) + } + } + case *stackitem.Struct: + if t.DecRC() == 0 { + for _, it := range t.Value().([]stackitem.Item) { + r.Remove(it) + } } case *stackitem.Map: - elems := t.Value().([]stackitem.MapElement) - for i := range elems { - r.Remove(elems[i].Key) - r.Remove(elems[i].Value) + if t.DecRC() == 0 { + elems := t.Value().([]stackitem.MapElement) + for i := range elems { + r.Remove(elems[i].Key) + r.Remove(elems[i].Value) + } } } }