Skip to content

Instantly share code, notes, and snippets.

@JeremyKuhne
Created March 21, 2018 02:29
Show Gist options
  • Save JeremyKuhne/2b503df20e61f143341d32d8c247837f to your computer and use it in GitHub Desktop.
Save JeremyKuhne/2b503df20e61f143341d32d8c247837f to your computer and use it in GitHub Desktop.
using System;
using System.Buffers;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
static class PoolingBenchmark
{
static void Main()
{
var pools = new[]
{
ArrayPool<byte>.Shared,
// ArrayPool<byte>.Create()
};
foreach (int numBuffers in new[] { 4, 2, 1 })
{
foreach (int workPerBuffer in new[] { 1, 256, 4096 })
{
foreach (var yield in new AwaitYieldApproach[] { new NoYield(), new LocalYield(), new GlobalYield() })
{
foreach (ArrayPool<byte> pool in pools)
{
Time(pool, 100000, numBuffers, workPerBuffer, yield);
}
}
}
}
}
private static async Task<int> Work(ArrayPool<byte> pool, int iters, int numBuffers, int workPerBuffer, AwaitYieldApproach yield)
{
int sum = 0;
var buffers = new byte[numBuffers][];
for (int iter = 0; iter < iters; iter++)
{
for (int i = 0; i < numBuffers; i++) buffers[i] = pool.Rent(4096);
foreach (byte[] buffer in buffers)
{
for (int i = 0; i < workPerBuffer; i++) buffer[i] = (byte)i;
await yield;
for (int i = 0; i < workPerBuffer; i++) sum += buffer[i];
}
for (int i = numBuffers - 1; i >= 0; --i) pool.Return(buffers[i]);
}
return sum;
}
private abstract class AwaitYieldApproach : ICriticalNotifyCompletion
{
public AwaitYieldApproach GetAwaiter() => this;
public void GetResult() { }
public abstract bool IsCompleted { get; }
public abstract void OnCompleted(Action action);
public void UnsafeOnCompleted(Action action) => OnCompleted(action);
}
private sealed class NoYield : AwaitYieldApproach
{
public override bool IsCompleted => true;
public override void OnCompleted(Action action) { }
}
private sealed class LocalYield : AwaitYieldApproach
{
public override bool IsCompleted => false;
public override void OnCompleted(Action action) => Task.Run(action);
}
private sealed class GlobalYield : AwaitYieldApproach
{
public override bool IsCompleted => false;
public override void OnCompleted(Action action) => ThreadPool.QueueUserWorkItem(s => ((Action)s)(), action);
}
private static void Time(ArrayPool<byte> pool, int iters, int numBuffers, int workPerBuffer, AwaitYieldApproach yield)
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
int gen0 = 0, gen1 = 0, gen2 = 0;
var sw = new Stopwatch();
int threadCount = Environment.ProcessorCount;
using (var b = new Barrier(threadCount + 1))
{
for (int i = 0; i < threadCount; i++)
{
new Thread(() =>
{
b.SignalAndWait();
b.SignalAndWait();
Work(pool, iters, numBuffers, workPerBuffer, yield).Wait();
b.SignalAndWait();
}).Start();
}
b.SignalAndWait();
gen0 = GC.CollectionCount(0);
gen1 = GC.CollectionCount(1);
gen2 = GC.CollectionCount(2);
sw.Restart();
b.SignalAndWait();
b.SignalAndWait();
sw.Stop();
gen0 = GC.CollectionCount(0) - gen0;
gen1 = GC.CollectionCount(1) - gen1;
gen2 = GC.CollectionCount(2) - gen2;
}
Console.WriteLine($"{pool.GetType().GetTypeInfo().Name,38}(#Arr:{numBuffers,-2},Work/Arr:{workPerBuffer,-4}, {yield.GetType().Name,-11}) => Time: {sw.Elapsed}, GC0/1/2: {gen0,5} / {gen1,5} / {gen2,3}");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment