From 6fb6ec3f07aaeccb221927960df408d118131894 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Wed, 12 Apr 2023 23:44:07 +0200 Subject: [PATCH] RecyclableList performance improvements. --- Recyclable.Collections/RecyclableList.cs | 107 ++++++++++------------- 1 file changed, 46 insertions(+), 61 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 2a036fd..31eb0f2 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -14,9 +14,11 @@ namespace Recyclable.Collections private static readonly T[][] _emptyMemoryBlocksArray = new T[0][]; private static readonly T[] _emptyBlockArray = new T[0]; + //private MemoryBlockInfo _memoryBlockInfo; private int _blockSize; private byte _blockSizePow2Shift; private int _blockSizeMinus1; + private bool _blockPoolingRequired; private int _nextItemBlockIndex; private int _nextItemIndex; @@ -244,7 +246,7 @@ namespace Recyclable.Collections /// /// that needs to be resized. /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. - /// Pre-calculated bit shifting value for left & right shift operations against. + /// Pre-calculated bit shifting value for left & right shift operations against. /// The minimum no. of items MUST be able to store after . /// For performance reasons, MUST a power of 2. This simplifies a lot block & item /// index calculations, i.e. makes them logical operations on bits. @@ -253,16 +255,17 @@ namespace Recyclable.Collections /// /// The maximum no. of items can store. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - protected static long Resize(RecyclableList list, int minBlockSize, byte minBlockSizeBitShift, long newCapacity) + protected static long Resize(RecyclableList list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) + //protected static long Resize(RecyclableList list, in MemoryBlockInfo blockInfo, long newCapacity) { ArrayPool blockArrayPool = list._blockArrayPool; - int sourceBlockCount = checked((int)(list._capacity >> minBlockSizeBitShift)); - int requiredBlockCount = checked((int)(newCapacity >> minBlockSizeBitShift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0)); + int sourceBlockCount = checked((int)(list._capacity >> minBlockSizePow2Shift)); + int requiredBlockCount = checked((int)(newCapacity >> minBlockSizePow2Shift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0)); int blockIndex; Span memoryBlocksSpan; // Release excessive blocks if we're downsizing - if (requiredBlockCount < sourceBlockCount && (minBlockSize >= RecyclableDefaults.MinPooledArrayLength)) + if (requiredBlockCount < sourceBlockCount && list._blockPoolingRequired) { memoryBlocksSpan = new(list._memoryBlocks, requiredBlockCount, sourceBlockCount - requiredBlockCount); blockIndex = 0; @@ -276,7 +279,7 @@ namespace Recyclable.Collections // Allocate new memory block for all arrays T[][]? newMemoryBlocks = requiredBlockCount < RecyclableDefaults.MinPooledArrayLength - ? (new T[requiredBlockCount][]) + ? new T[requiredBlockCount][] : memoryBlocksPool.Rent(requiredBlockCount); // Copy arrays from the old memory block for all arrays @@ -296,7 +299,7 @@ namespace Recyclable.Collections if (requiredBlockCount > sourceBlockCount) { memoryBlocksSpan = new(newMemoryBlocks, sourceBlockCount, newMemoryBlocks.Length - sourceBlockCount); - if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) + if (list._blockPoolingRequired) { if (sourceBlockCount == 0) { @@ -324,7 +327,7 @@ namespace Recyclable.Collections } } - return (long)newMemoryBlocks.Length << minBlockSizeBitShift; + return (long)newMemoryBlocks.Length << minBlockSizePow2Shift; } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] @@ -336,105 +339,87 @@ namespace Recyclable.Collections int blockSize = list._blockSize; newCapacity = Resize(list, blockSize, list._blockSizePow2Shift, newCapacity); - if (newCapacity > 0 && blockSize != list._memoryBlocks[0].Length) + if (blockSize != list._memoryBlocks[0].Length && newCapacity > 0) { - blockSize = list._memoryBlocks[0].Length; - if (blockSize >= RecyclableDefaults.MinPooledArrayLength) - { - list._blockArrayPool = RecyclableArrayPool.Shared(blockSize); - } - - list._blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); - list._blockSize = blockSize; - list._blockSizeMinus1 = blockSize - 1; + SetupBlockArrayPooling(list, list._memoryBlocks[0].Length); + list._blockSizeMinus1 = list._blockSize - 1; } list._capacity = newCapacity; return newCapacity; } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static void SetupBlockArrayPooling(RecyclableList list, int blockSize, ArrayPool? blockArrayPool = null) + { + list._blockSize = blockSize; + list._blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); + if (blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + list._blockPoolingRequired = true; + list._blockArrayPool = blockArrayPool ?? RecyclableArrayPool.Shared(blockSize); + } + else + { + list._blockPoolingRequired = false; + list._blockArrayPool = RecyclableArrayPool.Null; + } + } + [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling public RecyclableList(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) +#pragma warning restore CS8618 { _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; if (expectedItemsCount > 0) { minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); - _blockArrayPool = minBlockSize >= RecyclableDefaults.MinPooledArrayLength - ? blockArrayPool ?? RecyclableArrayPool.Shared(minBlockSize) - : RecyclableArrayPool.Null; - - _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); + SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); _capacity = Resize(this, minBlockSize, _blockSizePow2Shift, expectedItemsCount.Value); if (_memoryBlocks!.Length > 0 && _blockSize != _memoryBlocks[0].Length) { - _blockSize = _memoryBlocks[0].Length; + SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); _blockSizeMinus1 = _blockSize - 1; - _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)_blockSize)); - if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) - { - _blockArrayPool = RecyclableArrayPool.Shared(_blockSize); - } } else { - _blockSize = minBlockSize; _blockSizeMinus1 = minBlockSize - 1; } } else { - _blockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); _blockSizeMinus1 = _blockSize - 1; - _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)_blockSize)); - _blockArrayPool = _blockSize >= RecyclableDefaults.MinPooledArrayLength - ? blockArrayPool ?? RecyclableArrayPool.Shared(_blockSize) - : RecyclableArrayPool.Null; - _memoryBlocks = _emptyMemoryBlocksArray; } } +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling public RecyclableList(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) +#pragma warning restore CS8618 { - memoryBlocksPool ??= _defaultMemoryBlocksPool; - _memoryBlocksPool = memoryBlocksPool; - + _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; if (expectedItemsCount > 0) { minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); - _blockArrayPool = minBlockSize >= RecyclableDefaults.MinPooledArrayLength - ? blockArrayPool ?? RecyclableArrayPool.Shared(minBlockSize) - : RecyclableArrayPool.Null; - - _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); + SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); _capacity = Resize(this, minBlockSize, _blockSizePow2Shift, expectedItemsCount.Value); if (_memoryBlocks!.Length > 0 && minBlockSize != _memoryBlocks[0].Length) { - _blockSize = _memoryBlocks[0].Length; + SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); _blockSizeMinus1 = _blockSize - 1; - _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); - if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) - { - _blockArrayPool = RecyclableArrayPool.Shared(_blockSize); - } } else { - _blockSize = minBlockSize; _blockSizeMinus1 = minBlockSize - 1; } } else { - _blockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); - _blockArrayPool = _blockSize >= RecyclableDefaults.MinPooledArrayLength - ? blockArrayPool ?? RecyclableArrayPool.Shared(_blockSize) - : RecyclableArrayPool.Null; - + SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); _blockSizeMinus1 = _blockSize - 1; - _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)_blockSize)); _memoryBlocks = _emptyMemoryBlocksArray; } @@ -770,7 +755,7 @@ namespace Recyclable.Collections public void Clear() { - if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + if (_blockPoolingRequired) { Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); int memoryBlocksCount = memoryBlocksSpan.Length; @@ -870,13 +855,13 @@ namespace Recyclable.Collections public void RemoveBlock(int index) { - if (_blockSize < RecyclableDefaults.MinPooledArrayLength) + if (_blockPoolingRequired) { - _memoryBlocks[index] = _emptyBlockArray; + _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); } else { - _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); + _memoryBlocks[index] = _emptyBlockArray; } _capacity -= _blockSize;