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
{
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 _minBlockCount;
private int _blockCount;
private MemoryBucket<T>? _currentBucket;
private readonly SpinLock _updateLock = new(false);
private volatile int _blockCount;
private volatile MemoryBucket<T>? _currentBucket;
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)
{
_blockSize = blockSize;
_minBlockCount = minBlockCount;
_ = Task.Run(ReleaseMemoryOnExcess);
}
public override T[] Rent(int minimumLength)
@@ -24,8 +91,7 @@ namespace Recyclable.Collections.Pools
}
MemoryBucket<T>? bucket;
bool lockTaken = false;
_updateLock.Enter(ref lockTaken);
Enter();
if (_currentBucket != null)
{
_blockCount--;
@@ -37,14 +103,46 @@ namespace Recyclable.Collections.Pools
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];
}
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)
{
if (array.Length != _blockSize)
@@ -55,11 +153,12 @@ namespace Recyclable.Collections.Pools
MemoryBucket<T> bucket = new(array);
if (clearArray)
{
// TODO: Measure performance
// new Span<T>(array).Clear();
Array.Clear(array, 0, array.Length);
}
bool lockTaken = false;
_updateLock.Enter(ref lockTaken);
Enter();
if (_blockCount < _minBlockCount)
{
bucket.NextBucket = _currentBucket;
@@ -67,10 +166,7 @@ namespace Recyclable.Collections.Pools
_blockCount++;
}
if (lockTaken)
{
_updateLock.Exit(false);
}
Exit();
}
private static void ThrowWrongReturnedArraySizeException(int returnedLength, int blockSize)
@@ -1,35 +1,84 @@
using System.Buffers;
using System.Collections.Concurrent;
using System.Numerics;
namespace Recyclable.Collections.Pools
{
public static class RecyclableArrayPool<T>
public static class RecyclableArrayPool<T>
{
public static readonly OneSizeArrayPool<T> Null = new(0, 0);
private static readonly ConcurrentDictionary<int, ArrayPool<T>> _pools = new();
public static ArrayPool<T> Shared(int blockSize)
private enum MemoryPressure
{
//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);
pool = _pools.TryAdd(blockSize, pool) ? pool : _pools[blockSize];
previousValue = memoryExceeded;
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();
//try
//{
//}
//finally
//{
// _lock.Release();
//}
_ = Task.Run(CheckAndReportMemoryPressure);
}
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 Capacity = 1;
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 long ItemNotFoundIndexLong = -1L;
public const int ItemNotFoundIndex = -1;
@@ -1,5 +1,6 @@
using System.Buffers;
using System.Runtime.CompilerServices;
using Recyclable.Collections.Pools;
namespace Recyclable.Collections
{
@@ -223,8 +224,6 @@ namespace Recyclable.Collections
return values[middle];
}
internal static readonly ArrayPool<T> _arrayPool = ArrayPool<T>.Create();
private static readonly bool NeedsClearing = !typeof(T).IsValueType;
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
@@ -235,7 +234,7 @@ namespace Recyclable.Collections
T[] oldMemoryBlock = list._memoryBlock;
list._memoryBlock = (list._capacity <<= 1) < RecyclableDefaults.MinPooledArrayLength
? new T[list._capacity]
: _arrayPool.Rent(list._capacity);
: RecyclableArrayPool<T>.Rent(list._capacity);
// & WAS SLOWER WITHOUT
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.
_arrayPool.Return(oldMemoryBlock, NeedsClearing);
RecyclableArrayPool<T>.Return(oldMemoryBlock, NeedsClearing);
}
// list._memoryBlock = newMemoryBlock;
@@ -260,7 +259,7 @@ namespace Recyclable.Collections
public static void ResizeAndCopy(RecyclableList<T> sourceList, int newSize)
{
T[] newMemoryBlock = newSize >= RecyclableDefaults.MinPooledArrayLength
? _arrayPool.Rent(newSize)
? RecyclableArrayPool<T>.Rent(newSize)
: new T[newSize];
// & 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.
_arrayPool.Return(oldMemoryBlock, NeedsClearing);
RecyclableArrayPool<T>.Return(oldMemoryBlock, NeedsClearing);
}
sourceList._memoryBlock = newMemoryBlock;
+4 -3
View File
@@ -1,5 +1,6 @@
using System.Collections;
using System.Runtime.CompilerServices;
using Recyclable.Collections.Pools;
namespace Recyclable.Collections
{
@@ -80,7 +81,7 @@ namespace Recyclable.Collections
if (initialCapacity >= InitialCapacity)
{
_memoryBlock = initialCapacity >= RecyclableDefaults.MinPooledArrayLength
? RecyclableListHelpers<T>._arrayPool.Rent(initialCapacity)
? RecyclableArrayPool<T>.Rent(initialCapacity)
: new T[initialCapacity];
_capacity = _memoryBlock.Length;
}
@@ -138,7 +139,7 @@ namespace Recyclable.Collections
if (initialCapacity >= InitialCapacity)
{
_memoryBlock = initialCapacity >= RecyclableDefaults.MinPooledArrayLength
? RecyclableListHelpers<T>._arrayPool.Rent(initialCapacity)
? RecyclableArrayPool<T>.Rent(initialCapacity)
: new T[initialCapacity];
_capacity = _memoryBlock.Length;
}
@@ -515,7 +516,7 @@ namespace Recyclable.Collections
{
_capacity = 0;
// 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);
@@ -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>
/// Creates a set of new memory buffers, if needed, to allow storing at minimum <paramref name="newCapacity"/> no. of items.
/// </summary>
@@ -220,11 +211,6 @@ namespace Recyclable.Collections
{
if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength)
{
if (sourceBlockCount == 0)
{
minBlockSize = GetEstimatedBlockSize(minBlockSize, blockArrayPool);
}
blockIndex = sourceBlockCount;
while (true)
{