Created
December 1, 2025 05:17
-
-
Save devops-school/b5b348d9f43cafc4f1732b26fbb46c3f to your computer and use it in GitHub Desktop.
DOTNET: Memory Optimization in .NET with Object Pooling
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Buffers; | |
| using System.Diagnostics; | |
| class Program | |
| { | |
| static void Main() | |
| { | |
| const int iterations = 1_000_000; // Total loop count for the main test | |
| const int bufferSize = 1024; // Size of the byte[] buffer | |
| Console.WriteLine("========================================="); | |
| Console.WriteLine(" Object Pooling Demo (ArrayPool<byte>) "); | |
| Console.WriteLine("=========================================\n"); | |
| Console.WriteLine($"Iterations : {iterations:N0}"); | |
| Console.WriteLine($"BufferSize : {bufferSize} bytes\n"); | |
| // Warmup to avoid JIT noise in main measurements | |
| Console.WriteLine("Warming up JIT (small runs)..."); | |
| RunScenario("Warmup - Without Pooling", iterations / 10, bufferSize, usePool: false); | |
| RunScenario("Warmup - With Pooling", iterations / 10, bufferSize, usePool: true); | |
| Console.WriteLine(); | |
| Console.WriteLine("=========== REAL TESTS (Release) ==========\n"); | |
| RunScenario("WITHOUT pooling (new byte[] each time)", iterations, bufferSize, usePool: false); | |
| RunScenario("WITH pooling (ArrayPool<byte>.Shared)", iterations, bufferSize, usePool: true); | |
| Console.WriteLine("Done. Press any key to exit..."); | |
| Console.ReadKey(); | |
| } | |
| private static void RunScenario(string name, int iterations, int bufferSize, bool usePool) | |
| { | |
| // Force a GC before each scenario to start from a cleaner baseline | |
| GC.Collect(); | |
| GC.WaitForPendingFinalizers(); | |
| GC.Collect(); | |
| long gen0Before = GC.CollectionCount(0); | |
| long gen1Before = GC.CollectionCount(1); | |
| long gen2Before = GC.CollectionCount(2); | |
| long startAllocated = GC.GetTotalMemory(forceFullCollection: true); | |
| var sw = Stopwatch.StartNew(); | |
| long checksum = 0; // Used to prevent the JIT from optimizing the loops away | |
| if (!usePool) | |
| { | |
| // Scenario 1: NO POOLING | |
| for (int i = 0; i < iterations; i++) | |
| { | |
| // Allocate a new array EVERY TIME | |
| byte[] buffer = new byte[bufferSize]; | |
| // Simulate some work with the buffer | |
| for (int j = 0; j < bufferSize; j++) | |
| { | |
| buffer[j] = (byte)((i + j) & 0xFF); | |
| checksum += buffer[j]; | |
| } | |
| } | |
| } | |
| else | |
| { | |
| // Scenario 2: WITH POOLING | |
| ArrayPool<byte> pool = ArrayPool<byte>.Shared; | |
| for (int i = 0; i < iterations; i++) | |
| { | |
| // Rent a buffer from the shared pool (no allocation if a buffer is available) | |
| byte[] buffer = pool.Rent(bufferSize); | |
| try | |
| { | |
| // Only use the first bufferSize bytes | |
| for (int j = 0; j < bufferSize; j++) | |
| { | |
| buffer[j] = (byte)((i + j) & 0xFF); | |
| checksum += buffer[j]; | |
| } | |
| } | |
| finally | |
| { | |
| // Return buffer to the pool (clearArray: true is safer for real-world) | |
| pool.Return(buffer, clearArray: true); | |
| } | |
| } | |
| } | |
| sw.Stop(); | |
| long endAllocated = GC.GetTotalMemory(forceFullCollection: true); | |
| long gen0After = GC.CollectionCount(0); | |
| long gen1After = GC.CollectionCount(1); | |
| long gen2After = GC.CollectionCount(2); | |
| Console.WriteLine($"--- {name} ---"); | |
| Console.WriteLine($"Iterations: {iterations:N0}, BufferSize: {bufferSize} bytes"); | |
| Console.WriteLine($"Time Elapsed : {sw.ElapsedMilliseconds} ms"); | |
| Console.WriteLine($"GC Gen0 : {gen0After - gen0Before}"); | |
| Console.WriteLine($"GC Gen1 : {gen1After - gen1Before}"); | |
| Console.WriteLine($"GC Gen2 : {gen2After - gen2Before}"); | |
| long diffBytes = endAllocated - startAllocated; | |
| Console.WriteLine($"Managed Memory Delta (approx): {diffBytes / 1024.0 / 1024.0:F2} MB"); | |
| Console.WriteLine($"Checksum (ignore, just to keep JIT honest): {checksum}"); | |
| Console.WriteLine(); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment