-
Notifications
You must be signed in to change notification settings - Fork 144
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?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,159 @@ | ||
| // Copyright (C) 2015-2025 The Neo Project. | ||
| // | ||
| // ReferenceCounterBenchmarks.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 System; | ||
| using System.Collections.Generic; | ||
| using Array = Neo.VM.Types.Array; | ||
|
|
||
| namespace Neo.VM.Benchmarks; | ||
|
|
||
| [SimpleJob] | ||
| [MemoryDiagnoser] | ||
| public class ReferenceCounterBenchmarks | ||
| { | ||
| public enum Workload | ||
| { | ||
| NestedArrays, | ||
| DenseCycles, | ||
| StackChurn | ||
| } | ||
|
|
||
| [Params(nameof(ReferenceCounter), nameof(MarkSweepReferenceCounter))] | ||
| public string Strategy { get; set; } = nameof(ReferenceCounter); | ||
|
|
||
| [Params(Workload.NestedArrays, Workload.DenseCycles, Workload.StackChurn)] | ||
| public Workload Scenario { get; set; } | ||
|
|
||
| [Params(32)] | ||
| public int RootCount { get; set; } | ||
|
|
||
| [Params(4)] | ||
| public int Depth { get; set; } | ||
|
|
||
| [Params(4)] | ||
| public int FanOut { get; set; } | ||
|
|
||
| [Params(1024)] | ||
| public int Iterations { get; set; } | ||
|
|
||
| [Benchmark] | ||
| public int ExecuteScenario() | ||
| { | ||
| return Scenario switch | ||
| { | ||
| Workload.NestedArrays => CollectNestedArrays(), | ||
| Workload.DenseCycles => CollectDenseCycles(), | ||
| Workload.StackChurn => CollectStackChurn(), | ||
| _ => throw new ArgumentOutOfRangeException(nameof(Scenario)) | ||
| }; | ||
| } | ||
|
|
||
| private int CollectNestedArrays() | ||
| { | ||
| var counter = CreateCounter(); | ||
| var roots = new Array[RootCount]; | ||
|
|
||
| for (int i = 0; i < RootCount; i++) | ||
| { | ||
| var root = new Array(counter); | ||
| counter.AddStackReference(root); | ||
| roots[i] = root; | ||
| BuildNested(root, Depth, counter); | ||
| } | ||
|
|
||
| foreach (var root in roots) | ||
| counter.RemoveStackReference(root); | ||
|
|
||
| return counter.CheckZeroReferred(); | ||
| } | ||
|
|
||
| private void BuildNested(Array current, int depth, IReferenceCounter counter) | ||
| { | ||
| if (depth == 0) return; | ||
|
|
||
| var child = new Array(counter); | ||
| current.Add(child); | ||
| child.Add(current); | ||
|
|
||
| var sibling = new Array(counter); | ||
| current.Add(sibling); | ||
| sibling.Add(child); | ||
|
|
||
| BuildNested(child, depth - 1, counter); | ||
| } | ||
|
|
||
| private int CollectDenseCycles() | ||
| { | ||
| var counter = CreateCounter(); | ||
| var roots = new Array[RootCount]; | ||
| for (int i = 0; i < RootCount; i++) | ||
| { | ||
| roots[i] = new Array(counter); | ||
| counter.AddStackReference(roots[i]); | ||
| } | ||
|
|
||
| foreach (var root in roots) | ||
| BuildDenseCycles(root, Depth, counter, roots); | ||
|
|
||
| foreach (var root in roots) | ||
| counter.RemoveStackReference(root); | ||
|
|
||
| return counter.CheckZeroReferred(); | ||
| } | ||
|
|
||
| private void BuildDenseCycles(Array parent, int depth, IReferenceCounter counter, Array[] roots) | ||
| { | ||
| if (depth == 0) return; | ||
| for (int i = 0; i < FanOut; i++) | ||
| { | ||
| var child = new Array(counter); | ||
| parent.Add(child); | ||
| child.Add(parent); | ||
| if (i % 2 == 0) | ||
| child.Add(roots[(i + parent.Count) % roots.Length]); | ||
| BuildDenseCycles(child, depth - 1, counter, roots); | ||
| } | ||
| } | ||
|
|
||
| private int CollectStackChurn() | ||
| { | ||
| var counter = CreateCounter(); | ||
| List<Array> window = new(); | ||
| for (int i = 0; i < Iterations; i++) | ||
| { | ||
| var node = new Array(counter); | ||
| counter.AddStackReference(node); | ||
| if (window.Count > 0) | ||
| node.Add(window[i % window.Count]); | ||
| window.Add(node); | ||
| if (window.Count > FanOut) | ||
| { | ||
| var victim = window[0]; | ||
| counter.RemoveStackReference(victim); | ||
| window.RemoveAt(0); | ||
| } | ||
| } | ||
|
|
||
| foreach (var node in window) | ||
| counter.RemoveStackReference(node); | ||
|
|
||
| return counter.CheckZeroReferred(); | ||
| } | ||
|
|
||
| private IReferenceCounter CreateCounter() => Strategy switch | ||
| { | ||
| nameof(ReferenceCounter) => new ReferenceCounter(), | ||
| nameof(MarkSweepReferenceCounter) => new MarkSweepReferenceCounter(), | ||
| _ => throw new ArgumentOutOfRangeException(nameof(Strategy)) | ||
| }; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,171 @@ | ||||||||
| // Copyright (C) 2015-2025 The Neo Project. | ||||||||
| // | ||||||||
| // MarkSweepReferenceCounter.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 Neo.VM.Types; | ||||||||
| using System.Collections.Generic; | ||||||||
| using System.Runtime.CompilerServices; | ||||||||
|
|
||||||||
| namespace Neo.VM; | ||||||||
|
|
||||||||
| /// <summary> | ||||||||
| /// Reference counter that performs a deterministic mark-sweep collection. | ||||||||
| /// </summary> | ||||||||
| public sealed class MarkSweepReferenceCounter : IReferenceCounter | ||||||||
| { | ||||||||
| private readonly HashSet<StackItem> trackedItems = new(ReferenceEqualityComparer.Instance); | ||||||||
| private readonly HashSet<StackItem> zeroReferred = new(ReferenceEqualityComparer.Instance); | ||||||||
| private readonly Stack<StackItem> pending = new(); | ||||||||
| private readonly HashSet<StackItem> marked = new(ReferenceEqualityComparer.Instance); | ||||||||
| private readonly List<StackItem> unreachable = new(); | ||||||||
|
|
||||||||
| private int referencesCount; | ||||||||
|
|
||||||||
| /// <inheritdoc/> | ||||||||
| public int Count => referencesCount; | ||||||||
|
|
||||||||
| /// <inheritdoc/> | ||||||||
| public void AddZeroReferred(StackItem item) | ||||||||
| { | ||||||||
| if (zeroReferred.Add(item) && NeedTrack(item)) | ||||||||
| trackedItems.Add(item); | ||||||||
| } | ||||||||
|
|
||||||||
| /// <inheritdoc/> | ||||||||
| public void AddReference(StackItem item, CompoundType parent) | ||||||||
| { | ||||||||
| referencesCount++; | ||||||||
| if (!NeedTrack(item)) return; | ||||||||
|
|
||||||||
| Track(item); | ||||||||
|
|
||||||||
| item.ObjectReferences ??= new Dictionary<CompoundType, StackItem.ObjectReferenceEntry>(ReferenceEqualityComparer.Instance); | ||||||||
| if (!item.ObjectReferences.TryGetValue(parent, out var entry)) | ||||||||
| { | ||||||||
| entry = new StackItem.ObjectReferenceEntry(parent); | ||||||||
| item.ObjectReferences.Add(parent, entry); | ||||||||
| } | ||||||||
| entry.References++; | ||||||||
| } | ||||||||
|
|
||||||||
| /// <inheritdoc/> | ||||||||
| public void AddStackReference(StackItem item, int count = 1) | ||||||||
| { | ||||||||
| referencesCount += count; | ||||||||
| if (!NeedTrack(item)) return; | ||||||||
|
|
||||||||
| Track(item); | ||||||||
| item.StackReferences += count; | ||||||||
| zeroReferred.Remove(item); | ||||||||
| } | ||||||||
|
|
||||||||
| /// <inheritdoc/> | ||||||||
| public int CheckZeroReferred() | ||||||||
| { | ||||||||
| if (zeroReferred.Count == 0 || trackedItems.Count == 0) | ||||||||
| return referencesCount; | ||||||||
|
|
||||||||
| Collect(); | ||||||||
| return referencesCount; | ||||||||
| } | ||||||||
|
|
||||||||
| /// <inheritdoc/> | ||||||||
| public void RemoveReference(StackItem item, CompoundType parent) | ||||||||
| { | ||||||||
| referencesCount--; | ||||||||
| if (!NeedTrack(item)) return; | ||||||||
|
|
||||||||
| item.ObjectReferences![parent].References--; | ||||||||
| if (item.StackReferences == 0) | ||||||||
| zeroReferred.Add(item); | ||||||||
| } | ||||||||
|
|
||||||||
| /// <inheritdoc/> | ||||||||
| public void RemoveStackReference(StackItem item) | ||||||||
| { | ||||||||
| referencesCount--; | ||||||||
| if (!NeedTrack(item)) return; | ||||||||
|
|
||||||||
| if (--item.StackReferences == 0) | ||||||||
| zeroReferred.Add(item); | ||||||||
| } | ||||||||
|
|
||||||||
| /// <summary> | ||||||||
| /// Only compound types and buffers require tracking because they own other items or pooled memory. | ||||||||
| /// </summary> | ||||||||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||||||||
| private static bool NeedTrack(StackItem item) => item is CompoundType or Buffer; | ||||||||
shargon marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
|
|
||||||||
| private void Track(StackItem item) | ||||||||
| { | ||||||||
| if (trackedItems.Add(item)) | ||||||||
| zeroReferred.Add(item); | ||||||||
| } | ||||||||
|
|
||||||||
| private void Collect() | ||||||||
| { | ||||||||
|
||||||||
| { | |
| { | |
| pending.Clear(); |
shargon marked this conversation as resolved.
Show resolved
Hide resolved
shargon marked this conversation as resolved.
Show resolved
Hide resolved
shargon marked this conversation as resolved.
Show resolved
Hide resolved
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?
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.