-
Notifications
You must be signed in to change notification settings - Fork 143
Adopt mark-sweep GC and expand benchmarks #537
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
| /// <param name="jumpTable">The jump table to be used.</param> | ||
| public ExecutionEngine(JumpTable? jumpTable = null) | ||
| : this(jumpTable, new ReferenceCounter(), ExecutionEngineLimits.Default) { } | ||
| : this(jumpTable, new MarkSweepReferenceCounter(), ExecutionEngineLimits.Default) { } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's tested to be the same in mainnet and testnet?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will carry out the full scale test.
src/Neo.VM/EvaluationStack.cs
Outdated
|
|
||
| StackItem[] copyList = [.. _innerList]; | ||
| List<StackItem> reverseList = [.. copyList.Reverse()]; | ||
| List<StackItem> reverseList = [.. copyList.AsEnumerable().Reverse()]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's required?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces a mark-sweep garbage collection strategy as an alternative to the legacy reference counter, making it the new default for ExecutionEngine instances. The mark-sweep implementation aims to improve performance in high-pressure scenarios with complex circular references.
- Adds MarkSweepReferenceCounter class implementing IReferenceCounter interface
- Changes ExecutionEngine to use MarkSweepReferenceCounter by default
- Adds comprehensive benchmarks comparing both implementations under various workload scenarios
Reviewed Changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Neo.VM/MarkSweepReferenceCounter.cs | New mark-sweep GC implementation using reachability analysis to detect and collect unreachable circular references |
| src/Neo.VM/ExecutionEngine.cs | Updates default reference counter from ReferenceCounter to MarkSweepReferenceCounter |
| tests/Neo.VM.Tests/UT_ReferenceCounter.cs | Refactors existing tests to use helper methods and adds tests comparing both counter implementations |
| src/Neo.VM/EvaluationStack.cs | Adds explicit AsEnumerable() call for code clarity when reversing arrays |
| benchmarks/Neo.VM.Benchmarks/ReferenceCounterBenchmarks.cs | New benchmark suite with three workload scenarios (nested arrays, dense cycles, stack churn) to measure performance differences |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| using (ExecutionEngine engine = new()) | ||
| { | ||
| engine.LoadScript(script); | ||
| Assert.AreEqual(VMState.HALT, engine.Execute()); | ||
| Assert.AreEqual(expectedCount, engine.ReferenceCounter.Count); | ||
| } |
Copilot
AI
Nov 15, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test is comparing two MarkSweepReferenceCounter instances instead of comparing MarkSweepReferenceCounter with the legacy ReferenceCounter. Since the default ExecutionEngine now uses MarkSweepReferenceCounter (changed in ExecutionEngine.cs line 89), the first engine also uses MarkSweepReferenceCounter.
To properly test compatibility between the two implementations, the first engine should explicitly use the legacy ReferenceCounter:
using (CounterEngine engine = new(new ReferenceCounter()))There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's true
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| } | ||
|
|
||
| private void Collect() | ||
| { |
Copilot
AI
Nov 15, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The pending stack is reused across multiple Collect() calls but is not explicitly cleared at the start of Collect(). While it should normally be empty after Mark() completes, if an exception occurs during a previous Mark() operation, leftover items could contaminate subsequent collections, leading to incorrect marking behavior.
Consider adding pending.Clear(); at the start of the Collect() method to ensure a clean state for each collection cycle.
| { | |
| { | |
| pending.Clear(); |
|
Good job @Jim8y, speed up is almost 100%, and seems simpler than the previous algorithm |
|
|
||
| if (item is CompoundType compound) | ||
| { | ||
| referencesCount -= compound.SubItemsCount; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add assert referencesCount > compound.SubItemsCount here?
|
Good job. Conflicts now. |
|
Have you compared it to neo-project/neo#3581? |

Summary
Testing