|
|
|
|
using System;
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
|
|
|
|
|
namespace ZeroLevel.Services.Pools
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Steal from Roslyn
|
|
|
|
|
/// https://github.com/dotnet/roslyn/blob/master/src/Dependencies/PooledObjects/ObjectPool%601.cs
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class ObjectPool<T> where T : class
|
|
|
|
|
{
|
|
|
|
|
[DebuggerDisplay("{Value,nq}")]
|
|
|
|
|
private struct Element
|
|
|
|
|
{
|
|
|
|
|
internal T Value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
/// Not using System.Func{T} because this file is linked into the (debugger) Formatter,
|
|
|
|
|
/// which does not have that type (since it compiles against .NET 2.0).
|
|
|
|
|
/// </remarks>
|
|
|
|
|
public delegate T Factory();
|
|
|
|
|
|
|
|
|
|
// Storage for the pool objects. The first item is stored in a dedicated field because we
|
|
|
|
|
// expect to be able to satisfy most requests from it.
|
|
|
|
|
private T _firstItem;
|
|
|
|
|
|
|
|
|
|
private readonly Element[] _items;
|
|
|
|
|
|
|
|
|
|
// factory is stored for the lifetime of the pool. We will call this only when pool needs to
|
|
|
|
|
// expand. compared to "new T()", Func gives more flexibility to implementers and faster
|
|
|
|
|
// than "new T()".
|
|
|
|
|
private readonly Factory _factory;
|
|
|
|
|
|
|
|
|
|
public int Count => _items?.Length ?? 0;
|
|
|
|
|
|
|
|
|
|
public ObjectPool(Factory factory)
|
|
|
|
|
: this(factory, Environment.ProcessorCount * 2)
|
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
public ObjectPool(Factory factory, int size)
|
|
|
|
|
{
|
|
|
|
|
Debug.Assert(size >= 1);
|
|
|
|
|
_factory = factory;
|
|
|
|
|
_items = new Element[size - 1];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private T CreateInstance()
|
|
|
|
|
{
|
|
|
|
|
var inst = _factory();
|
|
|
|
|
return inst;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Produces an instance.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <remarks>
|
|
|
|
|
/// Search strategy is a simple linear probing which is chosen for it cache-friendliness.
|
|
|
|
|
/// Note that Free will try to store recycled objects close to the start thus statistically
|
|
|
|
|
/// reducing how far we will typically search.
|
|
|
|
|
/// </remarks>
|
|
|
|
|
public T Allocate()
|
|
|
|
|
{
|
|
|
|
|
// PERF: Examine the first element. If that fails, AllocateSlow will look at the remaining elements.
|
|
|
|
|
// Note that the initial read is optimistically not synchronized. That is intentional.
|
|
|
|
|
// We will interlock only when we have a candidate. in a worst case we may miss some
|
|
|
|
|
// recently returned objects. Not a big deal.
|
|
|
|
|
T inst = _firstItem;
|
|
|
|
|
if (inst == null || inst != Interlocked.CompareExchange(ref _firstItem, null, inst))
|
|
|
|
|
{
|
|
|
|
|
inst = AllocateSlow();
|
|
|
|
|
}
|
|
|
|
|
return inst;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private T AllocateSlow()
|
|
|
|
|
{
|
|
|
|
|
var items = _items;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < items.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
// Note that the initial read is optimistically not synchronized. That is intentional.
|
|
|
|
|
// We will interlock only when we have a candidate. in a worst case we may miss some
|
|
|
|
|
// recently returned objects. Not a big deal.
|
|
|
|
|
T inst = items[i].Value;
|
|
|
|
|
if (inst != null)
|
|
|
|
|
{
|
|
|
|
|
if (inst == Interlocked.CompareExchange(ref items[i].Value, null, inst))
|
|
|
|
|
{
|
|
|
|
|
return inst;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return CreateInstance();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns objects to the pool.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <remarks>
|
|
|
|
|
/// Search strategy is a simple linear probing which is chosen for it cache-friendliness.
|
|
|
|
|
/// Note that Free will try to store recycled objects close to the start thus statistically
|
|
|
|
|
/// reducing how far we will typically search in Allocate.
|
|
|
|
|
/// </remarks>
|
|
|
|
|
public void Free(T obj)
|
|
|
|
|
{
|
|
|
|
|
if (!Validate(obj))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (_firstItem == null)
|
|
|
|
|
{
|
|
|
|
|
// Intentionally not using interlocked here.
|
|
|
|
|
// In a worst case scenario two objects may be stored into same slot.
|
|
|
|
|
// It is very unlikely to happen and will only mean that one of the objects will get collected.
|
|
|
|
|
_firstItem = obj;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
FreeSlow(obj);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void FreeSlow(T obj)
|
|
|
|
|
{
|
|
|
|
|
var items = _items;
|
|
|
|
|
for (int i = 0; i < items.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
if (items[i].Value == null)
|
|
|
|
|
{
|
|
|
|
|
// Intentionally not using interlocked here.
|
|
|
|
|
// In a worst case scenario two objects may be stored into same slot.
|
|
|
|
|
// It is very unlikely to happen and will only mean that one of the objects will get collected.
|
|
|
|
|
items[i].Value = obj;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool Validate(object obj)
|
|
|
|
|
{
|
|
|
|
|
if (obj == null) return false;
|
|
|
|
|
if (_firstItem == obj) return false;
|
|
|
|
|
var items = _items;
|
|
|
|
|
for (int i = 0; i < items.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
var value = items[i].Value;
|
|
|
|
|
if (value == null)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (value == obj) return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
|
|
Alternate
|
|
|
|
|
https://stackoverflow.com/questions/1698738/objectpoolt-or-similar-for-net-already-in-a-library
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
}
|