Skip to content
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -250,3 +250,6 @@ paket-files/
# JetBrains Rider
.idea/
*.sln.iml

# Git worktrees (project-local)
.worktrees/
91 changes: 91 additions & 0 deletions benchmarks/Neo.VM.Benchmarks/Benchmarks_ArrayComparison.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright (C) 2015-2026 The Neo Project.
//
// Benchmarks_ArrayComparison.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using BenchmarkDotNet.Attributes;
using Neo.VM.Types;
using SysArray = System.Array;

namespace Neo.VM.Benchmarks.OpCodes;

/// <summary>
/// Direct comparison benchmarks showing the performance improvements
/// from the Array optimization (List→Array with pooling).
/// </summary>
[MemoryDiagnoser]
[InvocationCount(1)]
[IterationCount(15)]
[WarmupCount(5)]
public class Benchmarks_ArrayComparison
{
private const int ArraySize = 64;
private const int Iterations = 10000;

private ReferenceCounter _rc = null!;

[GlobalSetup]
public void Setup()
{
_rc = new ReferenceCounter();
}

// NEW OPTIMIZED IMPLEMENTATION
[Benchmark(Baseline = true)]
public void NewImplementation_WithPooling()
{
for (int i = 0; i < Iterations; i++)
{
var arr = new Neo.VM.Types.Array(_rc, StackItem.Null, ArraySize, skipReferenceCounting: true);
}
}

// LEGACY IMPLEMENTATION (simulated for comparison)
[Benchmark]
public void LegacyImplementation_WithList()
{
for (int i = 0; i < Iterations; i++)
{
var items = new StackItem[ArraySize];
SysArray.Fill(items, StackItem.Null);
var list = new System.Collections.Generic.List<StackItem>(items);
foreach (var item in list)
_rc.AddStackReference(item);
}
}

[Benchmark]
public void CreateAndDispose_Array()
{
for (int i = 0; i < Iterations; i++)
{
var arr = new Neo.VM.Types.Array(_rc, StackItem.Null, ArraySize, skipReferenceCounting: false);
// Simulate usage
for (int j = 0; j < ArraySize; j++)
{
var item = arr[j];
}
// Array is pooled, will be returned when GC'd
}
}

[Benchmark]
public void AddRemoveItems_Array()
{
for (int i = 0; i < Iterations; i++)
{
var arr = new Neo.VM.Types.Array(_rc, StackItem.Null, ArraySize, skipReferenceCounting: false);
// Access all items
for (int j = 0; j < ArraySize; j++)
{
_ = arr[j];
}
}
}
}
147 changes: 147 additions & 0 deletions benchmarks/Neo.VM.Benchmarks/Benchmarks_Optimizations.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Copyright (C) 2015-2026 The Neo Project.
//
// Benchmarks_Optimizations.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using BenchmarkDotNet.Attributes;
using Neo.VM.Benchmarks.Builders;
using Neo.VM.Types;
using BuilderInstruction = Neo.VM.Benchmarks.Builders.Instruction;
using SysArray = System.Array;

namespace Neo.VM.Benchmarks.OpCodes;

[MemoryDiagnoser]
[InvocationCount(1)]
[IterationCount(10)]
[WarmupCount(3)]
public class Benchmarks_Optimizations
{
private const int LoopIterations = 100000;
private const int SmallArraySize = 8;
private const int MediumArraySize = 32;
private const int LargeArraySize = 128;

private byte[] _newArraySmallScript = SysArray.Empty<byte>();
private byte[] _newArrayMediumScript = SysArray.Empty<byte>();
private byte[] _newArrayLargeScript = SysArray.Empty<byte>();
private byte[] _newStructScript = SysArray.Empty<byte>();
private byte[] _initSlotScript = SysArray.Empty<byte>();

[GlobalSetup]
public void Setup()
{
_newArraySmallScript = BuildLoop(OpCode.NEWARRAY, SmallArraySize);
_newArrayMediumScript = BuildLoop(OpCode.NEWARRAY, MediumArraySize);
_newArrayLargeScript = BuildLoop(OpCode.NEWARRAY, LargeArraySize);
_newStructScript = BuildLoop(OpCode.NEWSTRUCT, SmallArraySize);
_initSlotScript = BuildInitSlotScript(SmallArraySize);
}

[Benchmark(Baseline = true, OperationsPerInvoke = LoopIterations)]
public void NewArray_Small()
{
RunScript(_newArraySmallScript);
}

[Benchmark(OperationsPerInvoke = LoopIterations)]
public void NewArray_Medium()
{
RunScript(_newArrayMediumScript);
}

[Benchmark(OperationsPerInvoke = LoopIterations)]
public void NewArray_Large()
{
RunScript(_newArrayLargeScript);
}

[Benchmark(OperationsPerInvoke = LoopIterations)]
public void NewStruct()
{
RunScript(_newStructScript);
}

[Benchmark(OperationsPerInvoke = LoopIterations)]
public void InitSlot()
{
RunScript(_initSlotScript);
}

// Memory allocation benchmarks
[Benchmark]
public void CreateManyArrays_UsingPool()
{
using var engine = new SimpleBenchmarkEngine();
for (int i = 0; i < 10000; i++)
{
var arr = new Neo.VM.Types.Array(engine.ReferenceCounter, StackItem.Null, SmallArraySize, skipReferenceCounting: false);
}
}

private static void RunScript(byte[] script)
{
using var engine = new SimpleBenchmarkEngine();
engine.LoadScript(script);
while (engine.State != VMState.HALT && engine.State != VMState.FAULT)
{
engine.ExecuteNext();
}
}

private static byte[] BuildLoop(OpCode opCode, int size)
{
var builder = new InstructionBuilder();
builder.Push(LoopIterations);
builder.AddInstruction(OpCode.STLOC0);

var loopStart = new JumpTarget
{
_instruction = builder.AddInstruction(OpCode.NOP)
};

builder.Push(size);
builder.AddInstruction(opCode);
builder.AddInstruction(OpCode.DROP);

builder.AddInstruction(OpCode.LDLOC0);
builder.AddInstruction(OpCode.DEC);
builder.AddInstruction(OpCode.STLOC0);
builder.AddInstruction(OpCode.LDLOC0);
builder.Jump(OpCode.JMPIF, loopStart);
builder.Ret();
return builder.ToArray();
}

private static byte[] BuildInitSlotScript(int slotSize)
{
var builder = new InstructionBuilder();
// Push some items on stack
for (int i = 0; i < slotSize; i++)
{
builder.Push(i);
}
// InitSlot with arguments = slotSize, locals = 0
builder.AddInstruction(new BuilderInstruction
{
_opCode = OpCode.INITSLOT,
_operand = [0, (byte)slotSize]
});
builder.Ret();
return builder.ToArray();
}

private sealed class SimpleBenchmarkEngine : ExecutionEngine
{
public SimpleBenchmarkEngine()
: base(null, new ReferenceCounter(), ExecutionEngineLimits.Default)
{
}
}
}
105 changes: 105 additions & 0 deletions benchmarks/Neo.VM.Benchmarks/Benchmarks_ReferenceCounting.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright (C) 2015-2026 The Neo Project.
//
// Benchmarks_ReferenceCounting.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using BenchmarkDotNet.Attributes;
using Neo.VM.Types;
using VMArray = Neo.VM.Types.Array;
using VMBuffer = Neo.VM.Types.Buffer;

namespace Neo.VM.Benchmarks.OpCodes;

/// <summary>
/// Benchmarks for reference counting improvements, specifically
/// the new bulk AddReference method and HasTrackableSubItems optimization.
/// </summary>
[MemoryDiagnoser]
[InvocationCount(1)]
[IterationCount(15)]
[WarmupCount(5)]
public class Benchmarks_ReferenceCounting
{
private const int ArraySize = 16;
private const int Iterations = 1000;

private ReferenceCounter _rc = null!;
private StackItem _item = null!;

[GlobalSetup]
public void Setup()
{
_rc = new ReferenceCounter();
_item = new Integer(42);
}

// BULK ADDREFERENCE BENCHMARK
[Benchmark(Baseline = true)]
public void AddReference_SingleCalls()
{
var parent = new VMArray(_rc);
for (int i = 0; i < ArraySize; i++)
{
parent.Add(_item);
}
}

[Benchmark]
public void AddReference_BulkCall()
{
var parent = new VMArray(_rc, StackItem.Null, ArraySize, skipReferenceCounting: true);
_rc.AddReference(_item, parent, ArraySize);
}

// HAS_TRACKABLE_SUBITEMS BENCHMARK
[Benchmark]
public void ReferenceCount_WithTrackableItems()
{
var arr = new VMArray(_rc);
for (int i = 0; i < ArraySize; i++)
{
arr.Add(new VMBuffer(16));
}
// HasTrackableSubItems check is now O(1) instead of O(n)
_ = (arr as CompoundType)!.HasTrackableSubItems;
}

[Benchmark]
public void ReferenceCount_WithoutTrackableItems()
{
var arr = new VMArray(_rc);
for (int i = 0; i < ArraySize; i++)
{
arr.Add(Integer.Zero);
}
// HasTrackableSubItems check is now O(1) instead of O(n)
_ = (arr as CompoundType)!.HasTrackableSubItems;
}

// CREATE MANY ARRAYS
[Benchmark]
public void CreateManyArrays_Small()
{
for (int i = 0; i < Iterations; i++)
{
var arr = new VMArray(_rc, StackItem.Null, 8, skipReferenceCounting: false);
// Array will use pooling
}
}

[Benchmark]
public void CreateManyArrays_Large()
{
for (int i = 0; i < Iterations / 10; i++)
{
var arr = new VMArray(_rc, StackItem.Null, 128, skipReferenceCounting: false);
// Array will use pooling
}
}
}
Loading