Initial OneSizeArrayPool performance improvements.

This commit is contained in:
mlemanczyk committed 2023-07-08 14:45:24 +02:00
1 parent 5ee70abf93
commit 4da13cd9c1
6 files changed
+190 -59

No files matched your search

+110 -14
View File
@@ -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();//}
} }
} }
+1 -1
View File
@@ -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;
+4 -3
View File
@@ -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)
{ {