Skip to content

Instantly share code, notes, and snippets.

@dadhi
Last active June 29, 2018 07:00
Show Gist options
  • Save dadhi/091e7a1edcb2cc6e657b54fb7ba1a22d to your computer and use it in GitHub Desktop.
Save dadhi/091e7a1edcb2cc6e657b54fb7ba1a22d to your computer and use it in GitHub Desktop.
Simple object pool implementations benchmarked C# .NET
/*
StackPool is faster by far >10 times than ScanPool.
Other features:
- Does not consume memory from the start - it grows only when you Return object to it.
- Forgiving for no Return scenarios, that means when you not returning - memory is not consumed. So it is similar to `new`
- May be put a limit on depth, NOT tested yet.
*/
using System.Runtime.CompilerServices;
using System.Threading;
using BenchmarkDotNet.Attributes;
namespace Playground
{
[MemoryDiagnoser, DisassemblyDiagnoser]
public class ObjectPoolComparison
{
private static readonly ScanPool<X> _scanNoScanPool = new ScanPool<X>(0);
private static readonly ScanPool<X> _scanPool = new ScanPool<X>();
private static readonly StackPool<X> _stackPool = new StackPool<X>();
[Benchmark(Baseline = true)]
public int ScanPool_NoScan_2Rents2Returns()
{
var p = _scanNoScanPool;
var x1 = RentNoScanOrNew(p, true, 55, "55");
var x2 = RentNoScanOrNew(p, true, 66, "66");
p.ReturnNoScan(x1);
p.ReturnNoScan(x2);
var x3 = RentNoScanOrNew(p, true, 77, "77");
var x4 = RentNoScanOrNew(p, true, 77, "77");
return x3.I + x4.I;
}
[Benchmark]
public int ScanPool_2Rents2Returns()
{
var p = _scanPool;
var x1 = RentOrNew(p, true, 55, "55");
var x2 = RentOrNew(p, true, 66, "66");
p.Return(x1);
p.Return(x2);
var x3 = RentOrNew(p, true, 77, "77");
var x4 = RentOrNew(p, true, 77, "77");
return x3.I + x4.I;
}
[Benchmark]
public int StackPool_2Rents2Returns()
{
var p = _stackPool;
var x1 = RentOrNew(p, true, 55, "55");
var x2 = RentOrNew(p, true, 66, "66");
p.Return(x1);
p.Return(x2);
var x3 = RentOrNew(p, true, 77, "77");
var x4 = RentOrNew(p, true, 77, "77");
return x3.I + x4.I;
}
public static X RentNoScanOrNew(ScanPool<X> p, bool b, int i, string s) =>
p.RentNoScan()?.Init(b, i, s) ?? new X(b, i, s);
public static X RentOrNew(ScanPool<X> p, bool b, int i, string s) =>
p.Rent()?.Init(b, i, s) ?? new X(b, i, s);
public static X RentOrNew(StackPool<X> p, bool b, int i, string s) =>
p.Rent()?.Init(b, i, s) ?? new X(b, i, s);
public class X
{
public bool B;
public int I;
public string S;
public X(bool b, int i, string s)
{
B = b; I = i; S = s;
}
public X Init(bool b, int i, string s)
{
B = b; I = i; S = s;
return this;
}
}
}
public sealed class StackPool<T> where T : class
{
public T Rent() =>
Interlocked.Exchange(ref _s, _s?.Tail)?.Head;
public void Return(T x) =>
Interlocked.Exchange(ref _s, new Stack(x, _s));
private Stack _s;
private sealed class Stack
{
public readonly T Head;
public readonly Stack Tail;
public Stack(T h, Stack t)
{
Head = h;
Tail = t;
}
}
}
public sealed class ScanPool<T> where T : class
{
private T _x;
private readonly T[] _xs;
public ScanPool(int n = 32) { _xs = new T[n]; }
public T RentNoScan() => Interlocked.Exchange(ref _x, null);
public T Rent() =>
Interlocked.Exchange(ref _x, null) ??
ScanGet();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private T ScanGet()
{
T x = null;
var xs = _xs;
for (var i = 0; i < xs.Length &&
(x = Interlocked.Exchange(ref xs[i], null)) == null;
++i) {}
return x;
}
public void ReturnNoScan(T x) => Interlocked.Exchange(ref _x, x);
public void Return(T x)
{
if (Interlocked.CompareExchange(ref _x, x, null) != null)
ScanSet(x);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ScanSet(T x)
{
var xs = _xs;
for (var i = 0; i < xs.Length &&
Interlocked.CompareExchange(ref xs[i], x, null) != null;
++i) {}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment