Skip to content

Commit

Permalink
Add benchmark
Browse files Browse the repository at this point in the history
  • Loading branch information
martin-strecker-sonarsource committed Jan 8, 2025
1 parent dff46bd commit 3d7d2f5
Showing 1 changed file with 80 additions and 0 deletions.
80 changes: 80 additions & 0 deletions rules/S1215/csharp/rule.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,84 @@ There may be exceptions to this rule: for example, you've just triggered some ev
* https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/latency[Garbage collection latency modes]
* https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/performance#troubleshoot-performance-issues[Garbage collection troubleshoot performance issues]

=== Benchmarks

Each .NET runtime features distinct implementations, modes, and configurations for its garbage collector.
The benchmark below illustrates how invoking `GC.Collect()` can have opposite effects across different runtimes.

[options="header"]
|===
| Runtime | Collect | Mean | StdDev | Gen1 | Gen2 | Allocated
| .NET 9.0 | False | 659.2 ms | 15.69 ms | 21000.0000 | 6000.0000 | 205.95 MB
| .NET 9.0 | True | 888.8 ms | 15.34 ms | 21000.0000 | 7000.0000 | 205.95 MB
| | | | | | |
| .NET Framework 4.8.1 | False | 545.7 ms | 19.49 ms | 19000.0000 | 7000.0000 | 228.8 MB
| .NET Framework 4.8.1 | True | 484.8 ms | 11.79 ms | 19000.0000 | 7000.0000 | 228.8 MB
|===

==== Glossary

* **Mean** Arithmetic mean of all measurements
* **StdDev** Standard deviation of all measurements
* **Gen1** GC Generation 1 collects per 1000 operations
* **Gen2** GC Generation 2 collects per 1000 operations
* **Allocated** Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)
* **1 ms** 1 Millisecond (0.001 sec)

The results were generated by running the following snippet with https://github.com/dotnet/BenchmarkDotNet[BenchmarkDotNet]:

[source,csharp]
----
class Tree
{
public List<Tree> Children = new();
}
private void AppendToTree(Tree tree, int childsPerTree, int depth)
{
if (depth == 0)
{
return;
}
for (int i = 0; i < childsPerTree; i++)
{
var child = new Tree();
tree.Children.Add(child);
AppendToTree(child, childsPerTree, depth - 1);
}
}
[Benchmark]
[Arguments(true)]
[Arguments(false)]
public void Benchmark(bool collect)
{
var tree = new Tree();
AppendToTree(tree, 8, 7); // Create 8^7 Tree objects (2.097.152 objects) linked via List<Tree> Children
GC.Collect();
GC.Collect(); // Move the objects to generation 2
AppendToTree(new Tree(), 8, 6); // Add some more memory preasure (8^6 262.144 objects) which can be collected right after this call
tree = null; // Remove all references to the tree and its content. This freees up 8^7 Tree objects (2.097.152 objects)
if (collect)
{
GC.Collect(); // Force GC to run and block until it finishes
}
AppendToTree(new Tree(), 3, 10); // Do some more allocations (3^10 = 59.049)
AppendToTree(new Tree(), 4, 7); // 4^10 = 1.048.576
AppendToTree(new Tree(), 5, 7); // 5^7 = 78.125
GC.Collect(); // Collect all the memory allocated in this method
}
----

Hardware configuration:

[source]
----
BenchmarkDotNet v0.14.0, Windows 10 (10.0.19045.5247/22H2/2022Update)
Intel Core Ultra 7 165H, 1 CPU, 22 logical and 16 physical cores
[Host] : .NET Framework 4.8.1 (4.8.9282.0), X64 RyuJIT VectorSize=256
.NET 9.0 : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX2
.NET Framework 4.8.1 : .NET Framework 4.8.1 (4.8.9282.0), X64 RyuJIT VectorSize=256
----

include::rspecator.adoc[]

0 comments on commit 3d7d2f5

Please sign in to comment.