Skip to content

Instantly share code, notes, and snippets.

@devops-school
Created December 1, 2025 05:17
Show Gist options
  • Select an option

  • Save devops-school/b5b348d9f43cafc4f1732b26fbb46c3f to your computer and use it in GitHub Desktop.

Select an option

Save devops-school/b5b348d9f43cafc4f1732b26fbb46c3f to your computer and use it in GitHub Desktop.
DOTNET: Memory Optimization in .NET with Object Pooling
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