diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 0900eaf..7b7c433 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -253,6 +253,10 @@ namespace Recyclable.Collections /// /// This method checks for integral overflow. /// + /// + /// 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. + /// /// The maximum no. of items can store. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] protected static long Resize(RecyclableList list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) @@ -263,65 +267,65 @@ namespace Recyclable.Collections int blockIndex; Span 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 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 blockArrayPool) + { + var array = blockArrayPool.Rent(minBlockSize); + minBlockSize = array.Length; + blockArrayPool.Return(array); + return minBlockSize; + } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] protected static void SetupBlockArrayPooling(RecyclableList list, int blockSize, ArrayPool? blockArrayPool = null) { @@ -347,7 +360,7 @@ namespace Recyclable.Collections } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] - public static long EnsureCapacity(RecyclableList list, long requestedCapacity) + protected static long EnsureCapacity(RecyclableList 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;