Initial OneSizeArrayPool performance improvements.
This commit is contained in:
1 parent
5ee70abf93
commit
4da13cd9c1
6 files changed
+190
-59
No files matched your search
@@ -2,18 +2,85 @@
|
|||||||
|
|
||||||
namespace Recyclable.Collections.Pools
|
namespace Recyclable.Collections.Pools
|
||||||
{
|
{
|
||||||
public sealed class OneSizeArrayPool<T>
|
public sealed class OneSizeArrayPool<T> : ArrayPool<T>
|
||||||
{
|
{
|
||||||
|
private const int MemoryReleaseTaskSleepMilliseconds = 500;
|
||||||
|
internal bool _memoryExceeded;
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
|
~OneSizeArrayPool()
|
||||||
|
{
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ReleaseMemoryOnExcess()
|
||||||
|
{
|
||||||
|
while (!_disposed)
|
||||||
|
{
|
||||||
|
if (_memoryExceeded)
|
||||||
|
{
|
||||||
|
Enter();
|
||||||
|
if (_currentBucket != null)
|
||||||
|
{
|
||||||
|
_blockCount--;
|
||||||
|
_currentBucket = _currentBucket.NextBucket;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_currentBucket != null)
|
||||||
|
{
|
||||||
|
_blockCount--;
|
||||||
|
_currentBucket = _currentBucket.NextBucket;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_currentBucket != null)
|
||||||
|
{
|
||||||
|
_blockCount--;
|
||||||
|
_currentBucket = _currentBucket.NextBucket;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_currentBucket != null)
|
||||||
|
{
|
||||||
|
_blockCount--;
|
||||||
|
_currentBucket = _currentBucket.NextBucket;
|
||||||
|
}
|
||||||
|
|
||||||
|
Exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(MemoryReleaseTaskSleepMilliseconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private readonly int _blockSize;
|
private readonly int _blockSize;
|
||||||
private readonly int _minBlockCount;
|
private readonly int _minBlockCount;
|
||||||
private int _blockCount;
|
private volatile int _blockCount;
|
||||||
private MemoryBucket<T>? _currentBucket;
|
private volatile MemoryBucket<T>? _currentBucket;
|
||||||
private readonly SpinLock _updateLock = new(false);
|
private volatile int _locked;
|
||||||
|
|
||||||
|
private void Enter()
|
||||||
|
{
|
||||||
|
if (Interlocked.CompareExchange(ref _locked, 1, 0) == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpinWait waiter = new();
|
||||||
|
while (Interlocked.CompareExchange(ref _locked, 1, 0) == 1)
|
||||||
|
{
|
||||||
|
waiter.SpinOnce();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Exit()
|
||||||
|
{
|
||||||
|
_locked = 0;
|
||||||
|
}
|
||||||
|
|
||||||
public OneSizeArrayPool(int blockSize, int minBlockCount)
|
public OneSizeArrayPool(int blockSize, int minBlockCount)
|
||||||
{
|
{
|
||||||
_blockSize = blockSize;
|
_blockSize = blockSize;
|
||||||
_minBlockCount = minBlockCount;
|
_minBlockCount = minBlockCount;
|
||||||
|
_ = Task.Run(ReleaseMemoryOnExcess);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override T[] Rent(int minimumLength)
|
public override T[] Rent(int minimumLength)
|
||||||
@@ -24,8 +91,7 @@ namespace Recyclable.Collections.Pools
|
|||||||
}
|
}
|
||||||
|
|
||||||
MemoryBucket<T>? bucket;
|
MemoryBucket<T>? bucket;
|
||||||
bool lockTaken = false;
|
Enter();
|
||||||
_updateLock.Enter(ref lockTaken);
|
|
||||||
if (_currentBucket != null)
|
if (_currentBucket != null)
|
||||||
{
|
{
|
||||||
_blockCount--;
|
_blockCount--;
|
||||||
@@ -37,14 +103,46 @@ namespace Recyclable.Collections.Pools
|
|||||||
bucket = null;
|
bucket = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lockTaken)
|
Exit();
|
||||||
|
return bucket != null ? bucket.Memory : new T[_blockSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
public T[] Rent()
|
||||||
|
{
|
||||||
|
MemoryBucket<T>? bucket;
|
||||||
|
Enter();
|
||||||
|
if (_currentBucket != null)
|
||||||
{
|
{
|
||||||
_updateLock.Exit(false);
|
_blockCount--;
|
||||||
|
bucket = _currentBucket;
|
||||||
|
_currentBucket = _currentBucket.NextBucket;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bucket = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Exit();
|
||||||
return bucket != null ? bucket.Memory : new T[_blockSize];
|
return bucket != null ? bucket.Memory : new T[_blockSize];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ReturnUnchecked(T[] array, bool clearArray)
|
||||||
|
{
|
||||||
|
MemoryBucket<T> bucket = new(array);
|
||||||
|
if (clearArray)
|
||||||
|
{
|
||||||
|
// TODO: Measure performance
|
||||||
|
// new Span<T>(array).Clear();
|
||||||
|
Array.Clear(array, 0, array.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
Enter();
|
||||||
|
bucket.NextBucket = _currentBucket;
|
||||||
|
_currentBucket = bucket;
|
||||||
|
_blockCount++;
|
||||||
|
Exit();
|
||||||
|
}
|
||||||
|
|
||||||
public override void Return(T[] array, bool clearArray = false)
|
public override void Return(T[] array, bool clearArray = false)
|
||||||
{
|
{
|
||||||
if (array.Length != _blockSize)
|
if (array.Length != _blockSize)
|
||||||
@@ -55,11 +153,12 @@ namespace Recyclable.Collections.Pools
|
|||||||
MemoryBucket<T> bucket = new(array);
|
MemoryBucket<T> bucket = new(array);
|
||||||
if (clearArray)
|
if (clearArray)
|
||||||
{
|
{
|
||||||
|
// TODO: Measure performance
|
||||||
|
// new Span<T>(array).Clear();
|
||||||
Array.Clear(array, 0, array.Length);
|
Array.Clear(array, 0, array.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool lockTaken = false;
|
Enter();
|
||||||
_updateLock.Enter(ref lockTaken);
|
|
||||||
if (_blockCount < _minBlockCount)
|
if (_blockCount < _minBlockCount)
|
||||||
{
|
{
|
||||||
bucket.NextBucket = _currentBucket;
|
bucket.NextBucket = _currentBucket;
|
||||||
@@ -67,10 +166,7 @@ namespace Recyclable.Collections.Pools
|
|||||||
_blockCount++;
|
_blockCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lockTaken)
|
Exit();
|
||||||
{
|
|
||||||
_updateLock.Exit(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ThrowWrongReturnedArraySizeException(int returnedLength, int blockSize)
|
private static void ThrowWrongReturnedArraySizeException(int returnedLength, int blockSize)
|
||||||
|
|||||||
@@ -1,35 +1,84 @@
|
|||||||
using System.Buffers;
|
using System.Numerics;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
|
|
||||||
namespace Recyclable.Collections.Pools
|
namespace Recyclable.Collections.Pools
|
||||||
{
|
{
|
||||||
public static class RecyclableArrayPool<T>
|
public static class RecyclableArrayPool<T>
|
||||||
{
|
{
|
||||||
public static readonly OneSizeArrayPool<T> Null = new(0, 0);
|
private enum MemoryPressure
|
||||||
|
|
||||||
private static readonly ConcurrentDictionary<int, ArrayPool<T>> _pools = new();
|
|
||||||
|
|
||||||
public static ArrayPool<T> Shared(int blockSize)
|
|
||||||
{
|
{
|
||||||
//lock (_lock)
|
High,
|
||||||
|
Medium,
|
||||||
|
Low
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MemoryPressure GetMemoryPressure()
|
||||||
|
{
|
||||||
|
const double HighPressureThreshold = .90; // Percent of GC memory pressure threshold we consider "high"
|
||||||
|
const double MediumPressureThreshold = .70; // Percent of GC memory pressure threshold we consider "medium"
|
||||||
|
|
||||||
|
GCMemoryInfo memoryInfo = GC.GetGCMemoryInfo();
|
||||||
|
|
||||||
|
if (memoryInfo.MemoryLoadBytes >= memoryInfo.HighMemoryLoadThresholdBytes * HighPressureThreshold)
|
||||||
|
{
|
||||||
|
return MemoryPressure.High;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memoryInfo.MemoryLoadBytes >= memoryInfo.HighMemoryLoadThresholdBytes * MediumPressureThreshold)
|
||||||
|
{
|
||||||
|
return MemoryPressure.Medium;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MemoryPressure.Low;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task CheckAndReportMemoryPressure()
|
||||||
|
{
|
||||||
|
var previousValue = false;
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
if (!_pools.TryGetValue(blockSize, out ArrayPool<T>? pool))
|
bool memoryExceeded = GetMemoryPressure() == MemoryPressure.High;
|
||||||
|
if (memoryExceeded != previousValue)
|
||||||
{
|
{
|
||||||
pool = new OneSizeArrayPool<T>(blockSize, 100);
|
previousValue = memoryExceeded;
|
||||||
pool = _pools.TryAdd(blockSize, pool) ? pool : _pools[blockSize];
|
for (var i = 0; i < _pools!.Length; i++)
|
||||||
|
{
|
||||||
|
_pools[i]._memoryExceeded = memoryExceeded;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return pool;
|
await Task.Delay(500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static RecyclableArrayPool()
|
||||||
|
{
|
||||||
|
var pools = _pools;
|
||||||
|
for (var arraySize = 0; arraySize < 32; arraySize++)
|
||||||
|
{
|
||||||
|
int blockSize = 2 << arraySize;
|
||||||
|
pools[arraySize] = new OneSizeArrayPool<T>(blockSize, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
//_lock.Wait();
|
_ = Task.Run(CheckAndReportMemoryPressure);
|
||||||
//try
|
|
||||||
//{
|
|
||||||
//}
|
|
||||||
//finally
|
|
||||||
//{
|
|
||||||
// _lock.Release();
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static readonly OneSizeArrayPool<T> Null = new(0, 0);
|
||||||
|
|
||||||
|
private static readonly OneSizeArrayPool<T>[] _pools = new OneSizeArrayPool<T>[32];
|
||||||
|
public static T[] Rent(int blockSize) => _pools[31 - BitOperations.LeadingZeroCount((uint)blockSize) - 1].Rent();
|
||||||
|
public static void Return(in T[] memoryBlock, bool needsClearing) => _pools[31 - BitOperations.LeadingZeroCount((uint)memoryBlock.Length) - 1].ReturnUnchecked(memoryBlock, needsClearing);
|
||||||
|
|
||||||
|
// private static readonly ConcurrentDictionary<int, ArrayPool<T>> _pools = new();
|
||||||
|
|
||||||
|
public static OneSizeArrayPool<T> Shared(int blockSize) =>
|
||||||
|
//lock (_lock)
|
||||||
|
//{
|
||||||
|
|
||||||
|
// if (!_pools.TryGetValue(blockSize, out ArrayPool<T>? pool))
|
||||||
|
// {
|
||||||
|
// pool = new OneSizeArrayPool<T>(blockSize, 100);
|
||||||
|
// pool = _pools.TryAdd(blockSize, pool) ? pool : _pools[blockSize];
|
||||||
|
// }
|
||||||
|
_pools[31 - BitOperations.LeadingZeroCount((uint)blockSize) - 1];//}//_lock.Wait();//try//{//}//finally//{// _lock.Release();//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
public const int BlockSize = 16_384;
|
public const int BlockSize = 16_384;
|
||||||
public const int Capacity = 1;
|
public const int Capacity = 1;
|
||||||
public const long MinItemsCountForParallelization = 850_000;
|
public const long MinItemsCountForParallelization = 850_000;
|
||||||
public const int MinPooledArrayLength = 128;
|
public const int MinPooledArrayLength = 37;
|
||||||
public const int MaxPooledBlockSize = 2_147_483_591;
|
public const int MaxPooledBlockSize = 2_147_483_591;
|
||||||
public const long ItemNotFoundIndexLong = -1L;
|
public const long ItemNotFoundIndexLong = -1L;
|
||||||
public const int ItemNotFoundIndex = -1;
|
public const int ItemNotFoundIndex = -1;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using Recyclable.Collections.Pools;
|
||||||
|
|
||||||
namespace Recyclable.Collections
|
namespace Recyclable.Collections
|
||||||
{
|
{
|
||||||
@@ -223,8 +224,6 @@ namespace Recyclable.Collections
|
|||||||
return values[middle];
|
return values[middle];
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static readonly ArrayPool<T> _arrayPool = ArrayPool<T>.Create();
|
|
||||||
|
|
||||||
private static readonly bool NeedsClearing = !typeof(T).IsValueType;
|
private static readonly bool NeedsClearing = !typeof(T).IsValueType;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
@@ -235,7 +234,7 @@ namespace Recyclable.Collections
|
|||||||
T[] oldMemoryBlock = list._memoryBlock;
|
T[] oldMemoryBlock = list._memoryBlock;
|
||||||
list._memoryBlock = (list._capacity <<= 1) < RecyclableDefaults.MinPooledArrayLength
|
list._memoryBlock = (list._capacity <<= 1) < RecyclableDefaults.MinPooledArrayLength
|
||||||
? new T[list._capacity]
|
? new T[list._capacity]
|
||||||
: _arrayPool.Rent(list._capacity);
|
: RecyclableArrayPool<T>.Rent(list._capacity);
|
||||||
|
|
||||||
// & WAS SLOWER WITHOUT
|
// & WAS SLOWER WITHOUT
|
||||||
new Span<T>(oldMemoryBlock).CopyTo(list._memoryBlock);
|
new Span<T>(oldMemoryBlock).CopyTo(list._memoryBlock);
|
||||||
@@ -249,7 +248,7 @@ namespace Recyclable.Collections
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
// // If anything, it has been already cleared above, so we don't need to repeat it.
|
// // If anything, it has been already cleared above, so we don't need to repeat it.
|
||||||
_arrayPool.Return(oldMemoryBlock, NeedsClearing);
|
RecyclableArrayPool<T>.Return(oldMemoryBlock, NeedsClearing);
|
||||||
}
|
}
|
||||||
|
|
||||||
// list._memoryBlock = newMemoryBlock;
|
// list._memoryBlock = newMemoryBlock;
|
||||||
@@ -260,7 +259,7 @@ namespace Recyclable.Collections
|
|||||||
public static void ResizeAndCopy(RecyclableList<T> sourceList, int newSize)
|
public static void ResizeAndCopy(RecyclableList<T> sourceList, int newSize)
|
||||||
{
|
{
|
||||||
T[] newMemoryBlock = newSize >= RecyclableDefaults.MinPooledArrayLength
|
T[] newMemoryBlock = newSize >= RecyclableDefaults.MinPooledArrayLength
|
||||||
? _arrayPool.Rent(newSize)
|
? RecyclableArrayPool<T>.Rent(newSize)
|
||||||
: new T[newSize];
|
: new T[newSize];
|
||||||
|
|
||||||
// & WAS SLOWER WITHOUT
|
// & WAS SLOWER WITHOUT
|
||||||
@@ -278,7 +277,7 @@ namespace Recyclable.Collections
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
// If anything, it has been already cleared above, so we don't need to repeat it.
|
// If anything, it has been already cleared above, so we don't need to repeat it.
|
||||||
_arrayPool.Return(oldMemoryBlock, NeedsClearing);
|
RecyclableArrayPool<T>.Return(oldMemoryBlock, NeedsClearing);
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceList._memoryBlock = newMemoryBlock;
|
sourceList._memoryBlock = newMemoryBlock;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using Recyclable.Collections.Pools;
|
||||||
|
|
||||||
namespace Recyclable.Collections
|
namespace Recyclable.Collections
|
||||||
{
|
{
|
||||||
@@ -80,7 +81,7 @@ namespace Recyclable.Collections
|
|||||||
if (initialCapacity >= InitialCapacity)
|
if (initialCapacity >= InitialCapacity)
|
||||||
{
|
{
|
||||||
_memoryBlock = initialCapacity >= RecyclableDefaults.MinPooledArrayLength
|
_memoryBlock = initialCapacity >= RecyclableDefaults.MinPooledArrayLength
|
||||||
? RecyclableListHelpers<T>._arrayPool.Rent(initialCapacity)
|
? RecyclableArrayPool<T>.Rent(initialCapacity)
|
||||||
: new T[initialCapacity];
|
: new T[initialCapacity];
|
||||||
_capacity = _memoryBlock.Length;
|
_capacity = _memoryBlock.Length;
|
||||||
}
|
}
|
||||||
@@ -138,7 +139,7 @@ namespace Recyclable.Collections
|
|||||||
if (initialCapacity >= InitialCapacity)
|
if (initialCapacity >= InitialCapacity)
|
||||||
{
|
{
|
||||||
_memoryBlock = initialCapacity >= RecyclableDefaults.MinPooledArrayLength
|
_memoryBlock = initialCapacity >= RecyclableDefaults.MinPooledArrayLength
|
||||||
? RecyclableListHelpers<T>._arrayPool.Rent(initialCapacity)
|
? RecyclableArrayPool<T>.Rent(initialCapacity)
|
||||||
: new T[initialCapacity];
|
: new T[initialCapacity];
|
||||||
_capacity = _memoryBlock.Length;
|
_capacity = _memoryBlock.Length;
|
||||||
}
|
}
|
||||||
@@ -515,7 +516,7 @@ namespace Recyclable.Collections
|
|||||||
{
|
{
|
||||||
_capacity = 0;
|
_capacity = 0;
|
||||||
// If anything, it has been already cleared by .Clear(), Remove() or RemoveAt() methods, as the list was modified / disposed.
|
// If anything, it has been already cleared by .Clear(), Remove() or RemoveAt() methods, as the list was modified / disposed.
|
||||||
RecyclableListHelpers<T>._arrayPool.Return(_memoryBlock, false);
|
RecyclableArrayPool<T>.Return(_memoryBlock, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
|
|||||||
@@ -156,15 +156,6 @@ namespace Recyclable.Collections
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
|
||||||
private static int GetEstimatedBlockSize(int minBlockSize, ArrayPool<T> blockArrayPool)
|
|
||||||
{
|
|
||||||
var array = blockArrayPool.Rent(minBlockSize);
|
|
||||||
minBlockSize = array.Length;
|
|
||||||
blockArrayPool.Return(array);
|
|
||||||
return minBlockSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a set of new memory buffers, if needed, to allow storing at minimum <paramref name="newCapacity"/> no. of items.
|
/// Creates a set of new memory buffers, if needed, to allow storing at minimum <paramref name="newCapacity"/> no. of items.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -220,11 +211,6 @@ namespace Recyclable.Collections
|
|||||||
{
|
{
|
||||||
if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength)
|
if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength)
|
||||||
{
|
{
|
||||||
if (sourceBlockCount == 0)
|
|
||||||
{
|
|
||||||
minBlockSize = GetEstimatedBlockSize(minBlockSize, blockArrayPool);
|
|
||||||
}
|
|
||||||
|
|
||||||
blockIndex = sourceBlockCount;
|
blockIndex = sourceBlockCount;
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in new issue
Block a user