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.
/// </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>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
protected static long Resize(RecyclableList<T> list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity)
@@ -263,65 +267,65 @@ namespace Recyclable.Collections
int blockIndex;
Span<T[]> memoryBlocksSpan;
// Release excessive blocks if we're downsizing
if (requiredBlockCount < sourceBlockCount && list._blockPoolingRequired)
if (requiredBlockCount > (list._memoryBlocks?.Length ?? 0))
{
memoryBlocksSpan = new(list._memoryBlocks, requiredBlockCount, requiredBlockCount - sourceBlockCount);
blockIndex = 0;
while (blockIndex < memoryBlocksSpan.Length)
// Allocate new memory block for all arrays
T[][] newMemoryBlocks = requiredBlockCount >= RecyclableDefaults.MinPooledArrayLength ? list._memoryBlocksPool.Rent(requiredBlockCount) : new T[requiredBlockCount][];
// 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
if (requiredBlockCount > sourceBlockCount)
{
memoryBlocksSpan = new(newMemoryBlocks, sourceBlockCount, requiredBlockCount - sourceBlockCount);
memoryBlocksSpan = new(list._memoryBlocks!, sourceBlockCount, requiredBlockCount - sourceBlockCount);
if (list._blockPoolingRequired)
{
if (sourceBlockCount == 0)
{
memoryBlocksSpan[0] = blockArrayPool.Rent(minBlockSize);
minBlockSize = memoryBlocksSpan[0].Length;
blockIndex = 1;
}
else
{
blockIndex = 0;
minBlockSize = GetEstimatedBlockSize(minBlockSize, blockArrayPool);
}
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
{
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;
}
[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)]
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)]
public static long EnsureCapacity(RecyclableList<T> list, long requestedCapacity)
protected static long EnsureCapacity(RecyclableList<T> list, long requestedCapacity)
{
long newCapacity = list._capacity > 0
? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity))
@@ -377,7 +390,7 @@ namespace Recyclable.Collections
minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize));
SetupBlockArrayPooling(this, minBlockSize, blockArrayPool);
_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);
_blockSizeMinus1 = _blockSize - 1;