RecyclableList.Resize performance improvements.

This commit is contained in:
mlemanczyk committed 2023-04-17 00:11:23 +02:00
1 parent 60494c3219
commit 4600c4521e
1 file changed
+54 -41
+54 -41
View File
@@ -253,6 +253,10 @@ namespace Recyclable.Collections
/// ///
/// This method checks for integral overflow. /// This method checks for integral overflow.
/// </remarks> /// </remarks>
/// <remarks>
/// This method doesn't support downsizing the memory block. As such it doesn't release excessive blocks. This is to for additional performance
/// gain - 1+ operation less for each call. If you need this functionality, you need to implement it on a higher level.
/// </remarks>
/// <returns>The maximum no. of items <paramref name="list"/> can store.</returns> /// <returns>The maximum no. of items <paramref name="list"/> can store.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
protected static long Resize(RecyclableList<T> list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) protected static long Resize(RecyclableList<T> list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity)
@@ -263,65 +267,65 @@ namespace Recyclable.Collections
int blockIndex; int blockIndex;
Span<T[]> memoryBlocksSpan; Span<T[]> memoryBlocksSpan;
// Release excessive blocks if we're downsizing if (requiredBlockCount > (list._memoryBlocks?.Length ?? 0))
if (requiredBlockCount < sourceBlockCount && list._blockPoolingRequired)
{ {
memoryBlocksSpan = new(list._memoryBlocks, requiredBlockCount, requiredBlockCount - sourceBlockCount); // Allocate new memory block for all arrays
blockIndex = 0; T[][] newMemoryBlocks = requiredBlockCount >= RecyclableDefaults.MinPooledArrayLength ? list._memoryBlocksPool.Rent(requiredBlockCount) : new T[requiredBlockCount][];
while (blockIndex < memoryBlocksSpan.Length)
// Copy arrays from the old memory block for all arrays
if (sourceBlockCount > 0)
{ {
blockArrayPool.Return(memoryBlocksSpan[blockIndex++], NeedsClearing); Array.Copy(list._memoryBlocks!, newMemoryBlocks, sourceBlockCount);
// We can now return the old memory block for all arrays itself
if (sourceBlockCount >= RecyclableDefaults.MinPooledArrayLength)
{
list._memoryBlocksPool.Return(list._memoryBlocks!, true);
}
} }
list._memoryBlocks = newMemoryBlocks;
} }
ArrayPool<T[]> memoryBlocksPool = list._memoryBlocksPool;
// Allocate new memory block for all arrays
bool memoryBlockPoolingRequired = requiredBlockCount >= RecyclableDefaults.MinPooledArrayLength;
T[][] newMemoryBlocks = memoryBlockPoolingRequired ? memoryBlocksPool.Rent(requiredBlockCount) : new T[requiredBlockCount][];
// Copy arrays from the old memory block for all arrays
if (sourceBlockCount > 0)
{
Array.Copy(list._memoryBlocks, newMemoryBlocks, sourceBlockCount);
// We can now return the old memory block for all arrays itself
if (sourceBlockCount >= RecyclableDefaults.MinPooledArrayLength)
{
memoryBlocksPool.Return(list._memoryBlocks, true);
}
}
list._memoryBlocks = newMemoryBlocks;
// Allocate arrays for any new blocks // Allocate arrays for any new blocks
if (requiredBlockCount > sourceBlockCount) if (requiredBlockCount > sourceBlockCount)
{ {
memoryBlocksSpan = new(newMemoryBlocks, sourceBlockCount, requiredBlockCount - sourceBlockCount); memoryBlocksSpan = new(list._memoryBlocks!, sourceBlockCount, requiredBlockCount - sourceBlockCount);
if (list._blockPoolingRequired) if (list._blockPoolingRequired)
{ {
if (sourceBlockCount == 0) if (sourceBlockCount == 0)
{ {
memoryBlocksSpan[0] = blockArrayPool.Rent(minBlockSize); minBlockSize = GetEstimatedBlockSize(minBlockSize, blockArrayPool);
minBlockSize = memoryBlocksSpan[0].Length;
blockIndex = 1;
}
else
{
blockIndex = 0;
} }
while (blockIndex < memoryBlocksSpan.Length) blockIndex = 0;
do
{ {
memoryBlocksSpan[blockIndex++] = blockArrayPool.Rent(minBlockSize); memoryBlocksSpan[blockIndex] = blockArrayPool.Rent(minBlockSize);
} if (blockIndex + 1 < memoryBlocksSpan.Length)
{
blockIndex++;
}
else
{
break;
}
} while (true);
} }
else else
{ {
blockIndex = 0; blockIndex = 0;
while (blockIndex < memoryBlocksSpan.Length) do
{ {
memoryBlocksSpan[blockIndex++] = new T[minBlockSize]; memoryBlocksSpan[blockIndex] = new T[minBlockSize];
} if (blockIndex + 1 < memoryBlocksSpan.Length)
{
blockIndex++;
}
else
{
break;
}
} while (true);
} }
} }
@@ -329,6 +333,15 @@ namespace Recyclable.Collections
return (long)requiredBlockCount << minBlockSizePow2Shift; return (long)requiredBlockCount << minBlockSizePow2Shift;
} }
[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;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
protected static void SetupBlockArrayPooling(RecyclableList<T> list, int blockSize, ArrayPool<T>? blockArrayPool = null) protected static void SetupBlockArrayPooling(RecyclableList<T> list, int blockSize, ArrayPool<T>? blockArrayPool = null)
{ {
@@ -347,7 +360,7 @@ namespace Recyclable.Collections
} }
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
public static long EnsureCapacity(RecyclableList<T> list, long requestedCapacity) protected static long EnsureCapacity(RecyclableList<T> list, long requestedCapacity)
{ {
long newCapacity = list._capacity > 0 long newCapacity = list._capacity > 0
? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity))
@@ -377,7 +390,7 @@ namespace Recyclable.Collections
minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize));
SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); SetupBlockArrayPooling(this, minBlockSize, blockArrayPool);
_capacity = Resize(this, minBlockSize, _blockSizePow2Shift, expectedItemsCount.Value); _capacity = Resize(this, minBlockSize, _blockSizePow2Shift, expectedItemsCount.Value);
if (_blockSize != _memoryBlocks[0].Length && _capacity > 0) if (_blockSize != _memoryBlocks![0].Length && _capacity > 0)
{ {
SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool);
_blockSizeMinus1 = _blockSize - 1; _blockSizeMinus1 = _blockSize - 1;