From 3d7d2f543e792fd79f2c5cd6e99cf0174d22ff75 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Wed, 8 Jan 2025 11:19:45 +0100 Subject: [PATCH] Add benchmark --- rules/S1215/csharp/rule.adoc | 80 ++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/rules/S1215/csharp/rule.adoc b/rules/S1215/csharp/rule.adoc index ff4480ba20a..6afbb8fbddd 100644 --- a/rules/S1215/csharp/rule.adoc +++ b/rules/S1215/csharp/rule.adoc @@ -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 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 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[] \ No newline at end of file