From fc2335f5e47965995ae912e8014d062e2e2b44f0 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 15 Jul 2023 17:58:12 +0200 Subject: [PATCH] RecyclableArrayPool & OneSizeArrayPool converted to structs. --- ...leLongListPoc.Benchmarks.EnsureCapacity.cs | 42 ++-- Recyclable.Collections/Pools/MemoryUtils.cs | 32 +++ .../Pools/OneSizeArrayPool.cs | 136 ++++------- .../Pools/RecyclableArrayPool.cs | 76 ++----- .../RecyclableList.Helpers.cs | 40 +--- Recyclable.Collections/RecyclableList.cs | 30 +-- .../RecyclableLongList.Helpers.cs | 34 +-- Recyclable.Collections/RecyclableLongList.cs | 215 ++++++------------ Recyclable.Collections/ThrowHelper.cs | 5 + 9 files changed, 233 insertions(+), 377 deletions(-) create mode 100644 Recyclable.Collections/Pools/MemoryUtils.cs diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.EnsureCapacity.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.EnsureCapacity.cs index f83ec2d..599f55c 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.EnsureCapacity.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.EnsureCapacity.cs @@ -900,7 +900,7 @@ namespace Recyclable.Collections.Benchmarks.POC protected static readonly bool NeedsClearing = !typeof(T).IsValueType; protected ArrayPool _memoryBlocksPool; - protected ArrayPool _blockArrayPool; + protected OneSizeArrayPool _blockArrayPool; protected T[][] _memoryBlocks; private long _capacity; @@ -1133,7 +1133,6 @@ namespace Recyclable.Collections.Benchmarks.POC [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] protected static long Resize(RecyclableLongListV3 list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) { - ArrayPool blockArrayPool = list._blockArrayPool; int sourceBlockCount = list._reservedBlockCount; int requiredBlockCount = checked((int)(newCapacity >> minBlockSizePow2Shift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0)); int blockIndex; @@ -1146,7 +1145,7 @@ namespace Recyclable.Collections.Benchmarks.POC blockIndex = 0; while (blockIndex < memoryBlocksSpan.Length) { - blockArrayPool.Return(memoryBlocksSpan[blockIndex++], NeedsClearing); + list._blockArrayPool.ReturnUnchecked(memoryBlocksSpan[blockIndex++], NeedsClearing); } } @@ -1177,8 +1176,7 @@ namespace Recyclable.Collections.Benchmarks.POC { if (sourceBlockCount == 0) { - memoryBlocksSpan[0] = blockArrayPool.Rent(minBlockSize); - minBlockSize = memoryBlocksSpan[0].Length; + memoryBlocksSpan[0] = list._blockArrayPool.Rent(); blockIndex = 1; } else @@ -1188,7 +1186,7 @@ namespace Recyclable.Collections.Benchmarks.POC while (blockIndex < memoryBlocksSpan.Length) { - memoryBlocksSpan[blockIndex++] = blockArrayPool.Rent(minBlockSize); + memoryBlocksSpan[blockIndex++] = list._blockArrayPool.Rent(); } } else @@ -1206,14 +1204,14 @@ namespace Recyclable.Collections.Benchmarks.POC } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - protected static void SetupBlockArrayPooling(RecyclableLongListV3 list, int blockSize, ArrayPool? blockArrayPool = null) + protected static void SetupBlockArrayPooling(RecyclableLongListV3 list, int blockSize, ref OneSizeArrayPool blockArrayPool) { list._blockSize = blockSize; list._blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); if (blockSize >= RecyclableDefaults.MinPooledArrayLength) { list._blockPoolingRequired = true; - list._blockArrayPool = blockArrayPool ?? RecyclableArrayPool.Shared(blockSize); + list._blockArrayPool = blockArrayPool; } else { @@ -1233,7 +1231,7 @@ namespace Recyclable.Collections.Benchmarks.POC newCapacity = Resize(list, blockSize, list._blockSizePow2Shift, newCapacity); if (blockSize != list._memoryBlocks[0].Length && newCapacity > 0) { - SetupBlockArrayPooling(list, list._memoryBlocks[0].Length); + SetupBlockArrayPooling(list, list._memoryBlocks[0].Length, ref list._blockArrayPool); list._blockSizeMinus1 = list._blockSize - 1; } @@ -1243,7 +1241,7 @@ namespace Recyclable.Collections.Benchmarks.POC [MethodImpl(MethodImplOptions.AggressiveOptimization)] #pragma warning disable CS8618 // It's set by SetupBlockArrayPooling - public RecyclableLongListV3(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + public RecyclableLongListV3(ref OneSizeArrayPool blockArrayPool, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default) #pragma warning restore CS8618 { _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; @@ -1251,11 +1249,11 @@ namespace Recyclable.Collections.Benchmarks.POC if (expectedItemsCount > 0) { minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); - SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); + SetupBlockArrayPooling(this, minBlockSize, ref blockArrayPool); _capacity = Resize(this, minBlockSize, _blockSizePow2Shift, expectedItemsCount.Value); if (_blockSize != _memoryBlocks![0].Length && _capacity > 0) { - SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); + SetupBlockArrayPooling(this, _memoryBlocks[0].Length, ref blockArrayPool); _blockSizeMinus1 = _blockSize - 1; } else @@ -1265,25 +1263,25 @@ namespace Recyclable.Collections.Benchmarks.POC } else { - SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), ref blockArrayPool); _blockSizeMinus1 = _blockSize - 1; _memoryBlocks = _emptyMemoryBlocksArray; } } #pragma warning disable CS8618 // It's set by SetupBlockArrayPooling - public RecyclableLongListV3(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + public RecyclableLongListV3(ref OneSizeArrayPool blockArrayPool, IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default) #pragma warning restore CS8618 { _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; if (expectedItemsCount > 0) { minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); - SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); + SetupBlockArrayPooling(this, minBlockSize, ref blockArrayPool); _capacity = Resize(this, minBlockSize, _blockSizePow2Shift, expectedItemsCount.Value); if (minBlockSize != _memoryBlocks![0].Length && _capacity > 0) { - SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); + SetupBlockArrayPooling(this, _memoryBlocks[0].Length, ref blockArrayPool); _blockSizeMinus1 = _blockSize - 1; } else @@ -1293,7 +1291,7 @@ namespace Recyclable.Collections.Benchmarks.POC } else { - SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), ref blockArrayPool); _blockSizeMinus1 = _blockSize - 1; _memoryBlocks = _emptyMemoryBlocksArray; } @@ -1633,10 +1631,10 @@ namespace Recyclable.Collections.Benchmarks.POC { Span memoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); int memoryBlocksCount = memoryBlocksSpan.Length; - ArrayPool blockArrayPool = _blockArrayPool; + ref OneSizeArrayPool blockArrayPool = ref _blockArrayPool; for (int toRemoveIdx = 0; toRemoveIdx < memoryBlocksCount; toRemoveIdx++) { - blockArrayPool.Return(memoryBlocksSpan[toRemoveIdx], NeedsClearing); + blockArrayPool.ReturnUnchecked(memoryBlocksSpan[toRemoveIdx], NeedsClearing); memoryBlocksSpan[toRemoveIdx] = _emptyBlockArray; } } @@ -1733,7 +1731,7 @@ namespace Recyclable.Collections.Benchmarks.POC { if (_blockPoolingRequired) { - _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); + _blockArrayPool.ReturnUnchecked(_memoryBlocks[index], NeedsClearing); } else { @@ -1851,7 +1849,7 @@ namespace Recyclable.Collections.Benchmarks.POC public void RecyclableLongList_EnsureCapacityV3_ByPowOf2() { - using var list = new RecyclableLongListV3(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + using var list = new RecyclableLongListV3(ref RecyclableArrayPool._pools[BlockSize], minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); DoNothing(_ensureCapacityV3Func.Invoke(null, new object[] { list, TestObjectCount })); } @@ -1869,7 +1867,7 @@ namespace Recyclable.Collections.Benchmarks.POC public void RecyclableLongList_EnsureCapacityV3_ByBlockSize() { - using var list = new RecyclableLongListV3(); + using var list = new RecyclableLongListV3(ref RecyclableArrayPool._pools[BlockSize]); DoNothing(_ensureCapacityV3Func.Invoke(null, new object[] { list, TestObjectCount })); } diff --git a/Recyclable.Collections/Pools/MemoryUtils.cs b/Recyclable.Collections/Pools/MemoryUtils.cs new file mode 100644 index 0000000..c51306d --- /dev/null +++ b/Recyclable.Collections/Pools/MemoryUtils.cs @@ -0,0 +1,32 @@ +namespace Recyclable.Collections.Pools +{ + internal enum MemoryPressure + { + High, + Medium, + Low + } + + internal static class MemoryUtils + { + public 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; + } + } +} \ No newline at end of file diff --git a/Recyclable.Collections/Pools/OneSizeArrayPool.cs b/Recyclable.Collections/Pools/OneSizeArrayPool.cs index 04e441f..4c50db1 100644 --- a/Recyclable.Collections/Pools/OneSizeArrayPool.cs +++ b/Recyclable.Collections/Pools/OneSizeArrayPool.cs @@ -1,61 +1,13 @@ -using System.Buffers; - -namespace Recyclable.Collections.Pools +namespace Recyclable.Collections.Pools { - public sealed class OneSizeArrayPool : ArrayPool + public struct OneSizeArrayPool { - 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 volatile MemoryBucket? _currentBucket; + private volatile int _blockCount; + private volatile int _locked; private readonly int _blockSize; private readonly int _minBlockCount; - private volatile int _blockCount; - private volatile MemoryBucket? _currentBucket; - private volatile int _locked; + internal volatile bool _memoryExceeded; private void Enter() { @@ -78,35 +30,15 @@ namespace Recyclable.Collections.Pools public OneSizeArrayPool(int blockSize, int minBlockCount) { + _blockCount = 0; + _currentBucket = null; + _locked = 0; + _memoryExceeded = false; _blockSize = blockSize; _minBlockCount = minBlockCount; _ = Task.Run(ReleaseMemoryOnExcess); } - public override T[] Rent(int minimumLength) - { - if (minimumLength != _blockSize) - { - ThrowWrongRentedArraySizeException(minimumLength, _blockSize); - } - - MemoryBucket? bucket; - Enter(); - if (_currentBucket != null) - { - _blockCount--; - bucket = _currentBucket; - _currentBucket = _currentBucket.NextBucket; - } - else - { - bucket = null; - } - - Exit(); - return bucket != null ? bucket.Memory : new T[_blockSize]; - } - public T[] Rent() { MemoryBucket? bucket; @@ -143,14 +75,13 @@ namespace Recyclable.Collections.Pools Exit(); } - public override void Return(T[] array, bool clearArray = false) + public void Return(T[] array, bool clearArray = false) { if (array.Length != _blockSize) { - ThrowWrongReturnedArraySizeException(array.Length, _blockSize); + ThrowHelper.ThrowWrongReturnedArraySizeException(array.Length, _blockSize); } - MemoryBucket bucket = new(array); if (clearArray) { // TODO: Measure performance @@ -158,6 +89,7 @@ namespace Recyclable.Collections.Pools Array.Clear(array, 0, array.Length); } + MemoryBucket bucket = new(array); Enter(); if (_blockCount < _minBlockCount) { @@ -169,14 +101,44 @@ namespace Recyclable.Collections.Pools Exit(); } - private static void ThrowWrongReturnedArraySizeException(int returnedLength, int blockSize) - { - throw new InvalidOperationException($"The array size {returnedLength} doesn't match the size {blockSize} of the pool buffer and cannot be returned to this pool."); - } + private const int MemoryReleaseTaskSleepMilliseconds = 500; - private static void ThrowWrongRentedArraySizeException(int requestedSize, int blockSize) + private async Task ReleaseMemoryOnExcess() { - throw new InvalidOperationException($"The requested size {requestedSize} doesn't match the size {blockSize} of the pool buffer and cannot be taken from this pool."); + while (true) + { + 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); + } } } } diff --git a/Recyclable.Collections/Pools/RecyclableArrayPool.cs b/Recyclable.Collections/Pools/RecyclableArrayPool.cs index c8e3121..dfe5c66 100644 --- a/Recyclable.Collections/Pools/RecyclableArrayPool.cs +++ b/Recyclable.Collections/Pools/RecyclableArrayPool.cs @@ -2,41 +2,36 @@ using System.Numerics; namespace Recyclable.Collections.Pools { - public static class RecyclableArrayPool + public readonly struct RecyclableArrayPool { - private enum MemoryPressure + internal static readonly OneSizeArrayPool[] _pools = new OneSizeArrayPool[32]; + public static T[] RentShared(int blockSize) => _pools[31 - BitOperations.LeadingZeroCount((uint)blockSize)].Rent(); + public static void ReturnShared(in T[] memoryBlock, bool needsClearing) + => _pools[31 - BitOperations.LeadingZeroCount((uint)memoryBlock.Length)].ReturnUnchecked(memoryBlock, needsClearing); + + public static ref OneSizeArrayPool Shared(int blockSize) + => ref _pools[31 - BitOperations.LeadingZeroCount((uint)blockSize)]; + + static RecyclableArrayPool() { - High, - Medium, - Low + var pools = _pools; + for (var arraySize = 0; arraySize < 32; arraySize++) + { + int blockSize = 1 << arraySize; + pools[arraySize] = new OneSizeArrayPool(blockSize, 1000); + } + + _ = Task.Run(CheckAndReportMemoryPressure); } - 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; - } + public static readonly OneSizeArrayPool Null = new(0, 0); private static async Task CheckAndReportMemoryPressure() { var previousValue = false; while (true) { - bool memoryExceeded = GetMemoryPressure() == MemoryPressure.High; + bool memoryExceeded = MemoryUtils.GetMemoryPressure() == MemoryPressure.High; if (memoryExceeded != previousValue) { previousValue = memoryExceeded; @@ -49,36 +44,5 @@ namespace Recyclable.Collections.Pools await Task.Delay(500); } } - - static RecyclableArrayPool() - { - var pools = _pools; - for (var arraySize = 0; arraySize < 32; arraySize++) - { - int blockSize = 2 << arraySize; - pools[arraySize] = new OneSizeArrayPool(blockSize, 1000); - } - - _ = Task.Run(CheckAndReportMemoryPressure); - } - - public static readonly OneSizeArrayPool Null = new(0, 0); - - private static readonly OneSizeArrayPool[] _pools = new OneSizeArrayPool[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> _pools = new(); - - public static OneSizeArrayPool Shared(int blockSize) => - //lock (_lock) - //{ - - // if (!_pools.TryGetValue(blockSize, out ArrayPool? pool)) - // { - // pool = new OneSizeArrayPool(blockSize, 100); - // pool = _pools.TryAdd(blockSize, pool) ? pool : _pools[blockSize]; - // } - _pools[31 - BitOperations.LeadingZeroCount((uint)blockSize) - 1];//}//_lock.Wait();//try//{//}//finally//{// _lock.Release();//} } } diff --git a/Recyclable.Collections/RecyclableList.Helpers.cs b/Recyclable.Collections/RecyclableList.Helpers.cs index 4064e99..65d7c50 100644 --- a/Recyclable.Collections/RecyclableList.Helpers.cs +++ b/Recyclable.Collections/RecyclableList.Helpers.cs @@ -1,4 +1,5 @@ using System.Buffers; +using System.Numerics; using System.Runtime.CompilerServices; using Recyclable.Collections.Pools; @@ -6,6 +7,8 @@ namespace Recyclable.Collections { internal static class RecyclableListHelpers { + private static readonly bool NeedsClearing = !typeof(T).IsValueType; + [Obsolete("This method MAY be removed in the near future.")] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Contains(RecyclableList arrays, T item) => arrays.Any(x => x.Contains(item)); @@ -50,19 +53,6 @@ namespace Recyclable.Collections } } - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public static int EnsureCapacity(RecyclableList list, int requestedCapacity) - { - int newCapacity = list._capacity; - while (newCapacity < requestedCapacity) - { - newCapacity <<= 1; - } - - ResizeAndCopy(list, newCapacity); - return list._capacity; - } - [Obsolete("This method WILL be removed in the near future. Please use the existing enumerators instead of it.")] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static IEnumerable Enumerate(RecyclableList arrays, int chunkSize, long totalCount) @@ -224,8 +214,6 @@ namespace Recyclable.Collections return values[middle]; } - private static readonly bool NeedsClearing = !typeof(T).IsValueType; - [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] public static void ResizeAndCopy(RecyclableList list) { @@ -234,7 +222,7 @@ namespace Recyclable.Collections T[] oldMemoryBlock = list._memoryBlock; list._memoryBlock = (list._capacity <<= 1) < RecyclableDefaults.MinPooledArrayLength ? new T[list._capacity] - : RecyclableArrayPool.Rent(list._capacity); + : RecyclableArrayPool.RentShared(list._capacity); // & WAS SLOWER WITHOUT new Span(oldMemoryBlock, 0, list._count).CopyTo(list._memoryBlock); @@ -248,25 +236,22 @@ namespace Recyclable.Collections //} // // If anything, it has been already cleared above, so we don't need to repeat it. - RecyclableArrayPool.Return(oldMemoryBlock, NeedsClearing); + RecyclableArrayPool.ReturnShared(oldMemoryBlock, NeedsClearing); } - - // list._memoryBlock = newMemoryBlock; - list._capacity = list._memoryBlock.Length; } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] - public static void ResizeAndCopy(RecyclableList sourceList, int newSize) + public static int ResizeAndCopy(RecyclableList sourceList, int newSize) { - T[] newMemoryBlock = newSize >= RecyclableDefaults.MinPooledArrayLength - ? RecyclableArrayPool.Rent(newSize) + T[] oldMemoryBlock = sourceList._memoryBlock; + sourceList._memoryBlock = newSize >= RecyclableDefaults.MinPooledArrayLength + ? RecyclableArrayPool.RentShared(newSize) : new T[newSize]; // & WAS SLOWER WITHOUT - T[] oldMemoryBlock = sourceList._memoryBlock; // & WAS SLOWER AS ARRAY - new Span(oldMemoryBlock, 0, sourceList._count).CopyTo(newMemoryBlock); + new Span(oldMemoryBlock, 0, sourceList._count).CopyTo(sourceList._memoryBlock); if (oldMemoryBlock.Length >= RecyclableDefaults.MinPooledArrayLength) { @@ -277,11 +262,10 @@ namespace Recyclable.Collections //} // If anything, it has been already cleared above, so we don't need to repeat it. - RecyclableArrayPool.Return(oldMemoryBlock, NeedsClearing); + RecyclableArrayPool.ReturnShared(oldMemoryBlock, NeedsClearing); } - sourceList._memoryBlock = newMemoryBlock; - sourceList._capacity = newMemoryBlock.Length; + return newSize; } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index cdaba12..7818a70 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Numerics; using System.Runtime.CompilerServices; using Recyclable.Collections.Pools; @@ -35,7 +36,7 @@ namespace Recyclable.Collections { if (targetItemIdx + growByCount > capacity) { - capacity = RecyclableListHelpers.EnsureCapacity(this, capacity + growByCount); + capacity = RecyclableListHelpers.ResizeAndCopy(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)(capacity + growByCount)))); memorySpan = new(_memoryBlock); available = capacity - targetItemIdx; } @@ -52,13 +53,14 @@ namespace Recyclable.Collections while (i >= available); } + _capacity = capacity; _count = targetItemIdx; return enumerator; } private void AddRangeWithKnownCount(IEnumerable source, int targetItemIdx, int requiredAdditionalCapacity) { - _ = RecyclableListHelpers.EnsureCapacity(this, targetItemIdx + requiredAdditionalCapacity); + _capacity = RecyclableListHelpers.ResizeAndCopy(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)(targetItemIdx + requiredAdditionalCapacity)))); Span memorySpan = new(_memoryBlock); foreach (var item in source) @@ -80,7 +82,7 @@ namespace Recyclable.Collections if (initialCapacity >= RecyclableDefaults.InitialCapacity) { _memoryBlock = initialCapacity >= RecyclableDefaults.MinPooledArrayLength - ? RecyclableArrayPool.Rent(initialCapacity) + ? RecyclableArrayPool.RentShared(checked((int)BitOperations.RoundUpToPowerOf2((uint)initialCapacity))) : new T[initialCapacity]; _capacity = _memoryBlock.Length; } @@ -138,7 +140,7 @@ namespace Recyclable.Collections if (initialCapacity >= RecyclableDefaults.InitialCapacity) { _memoryBlock = initialCapacity >= RecyclableDefaults.MinPooledArrayLength - ? RecyclableArrayPool.Rent(initialCapacity) + ? RecyclableArrayPool.RentShared(checked((int)BitOperations.RoundUpToPowerOf2((uint)initialCapacity))) : new T[initialCapacity]; _capacity = _memoryBlock.Length; } @@ -172,7 +174,7 @@ namespace Recyclable.Collections { if (_capacity != value) { - RecyclableListHelpers.ResizeAndCopy(this, value); + _capacity = RecyclableListHelpers.ResizeAndCopy(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)value))); _version++; } } @@ -200,7 +202,7 @@ namespace Recyclable.Collections { if (_capacity < _count + items.Length) { - _ = RecyclableListHelpers.EnsureCapacity(this, _count + items.Length); + _capacity = RecyclableListHelpers.ResizeAndCopy(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)(_count + items.Length)))); } new Span(items).CopyTo(new Span(_memoryBlock, _count, items.Length)); @@ -213,7 +215,7 @@ namespace Recyclable.Collections { if (_capacity < _count + items.Length) { - _ = RecyclableListHelpers.EnsureCapacity(this, _count + items.Length); + _capacity = RecyclableListHelpers.ResizeAndCopy(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)(_count + items.Length)))); } items.CopyTo(new Span(_memoryBlock, _count, items.Length)); @@ -226,7 +228,7 @@ namespace Recyclable.Collections { if (_capacity < _count + items.Count) { - _ = RecyclableListHelpers.EnsureCapacity(this, _count + items.Count); + _capacity = RecyclableListHelpers.ResizeAndCopy(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)(_count + items.Count)))); } items.CopyTo(_memoryBlock, _count); @@ -239,7 +241,7 @@ namespace Recyclable.Collections { if (_capacity < _count + items.Count) { - _ = RecyclableListHelpers.EnsureCapacity(this, _count + items.Count); + _capacity = RecyclableListHelpers.ResizeAndCopy(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)(_count + items.Count)))); } items.CopyTo(_memoryBlock, _count); @@ -252,7 +254,7 @@ namespace Recyclable.Collections { if (_capacity < _count + items._count) { - _ = RecyclableListHelpers.EnsureCapacity(this, _count + items._count); + _capacity = RecyclableListHelpers.ResizeAndCopy(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)(_count + items._count)))); } new Span(items._memoryBlock, 0, items._count).CopyTo(new Span(_memoryBlock, _count, items._count)); @@ -282,7 +284,7 @@ namespace Recyclable.Collections int targetCapacity = _count + (int)sourceItemsCount; if (_capacity < targetCapacity) { - _ = RecyclableListHelpers.EnsureCapacity(this, targetCapacity); + _capacity = RecyclableListHelpers.ResizeAndCopy(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)targetCapacity))); } Span targetSpan = new Span(_memoryBlock)[_count..], @@ -291,7 +293,7 @@ namespace Recyclable.Collections int blockIndex, sourceBlockSize = items.BlockSize, // TODO: Convert to bit-shift - lastBlockIndex = (int)(sourceItemsCount / sourceBlockSize) - 1; + lastBlockIndex = (int)(sourceItemsCount >> items._blockSizePow2BitShift) - 1; for (blockIndex = 0; blockIndex <= lastBlockIndex; blockIndex++) { @@ -381,7 +383,7 @@ namespace Recyclable.Collections int requestedCapacity = oldCount + 1; if (_capacity < requestedCapacity) { - _ = RecyclableListHelpers.EnsureCapacity(this, requestedCapacity); + _capacity = RecyclableListHelpers.ResizeAndCopy(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)requestedCapacity))); } if (oldCount > 0) @@ -468,7 +470,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. - RecyclableArrayPool.Return(_memoryBlock, false); + RecyclableArrayPool.ReturnShared(_memoryBlock, false); } GC.SuppressFinalize(this); diff --git a/Recyclable.Collections/RecyclableLongList.Helpers.cs b/Recyclable.Collections/RecyclableLongList.Helpers.cs index 4227b34..1ec9fb6 100644 --- a/Recyclable.Collections/RecyclableLongList.Helpers.cs +++ b/Recyclable.Collections/RecyclableLongList.Helpers.cs @@ -107,25 +107,6 @@ namespace Recyclable.Collections } } - [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] - public static long EnsureCapacity(RecyclableLongList list, long requestedCapacity) - { - long newCapacity = list._capacity > 0 - ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) - : requestedCapacity; - - int blockSize = list._blockSize; - newCapacity = Resize(list, blockSize, list._blockSizePow2BitShift, newCapacity); - if (blockSize != list._memoryBlocks[0].Length && newCapacity > 0) - { - SetupBlockArrayPooling(list, list._memoryBlocks[0].Length); - list._blockSizeMinus1 = list._blockSize - 1; - } - - list._capacity = newCapacity; - return newCapacity; - } - public static IEnumerable Enumerate(T[][] arrays, int chunkSize, long totalCount) { long currentCount = 0; @@ -174,10 +155,9 @@ namespace Recyclable.Collections /// 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)] + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] public static long Resize(RecyclableLongList list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) { - ArrayPool blockArrayPool = list._blockArrayPool; int sourceBlockCount = list._reservedBlockCount; int requiredBlockCount = checked((int)(newCapacity >> minBlockSizePow2Shift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0)); int blockIndex; @@ -186,7 +166,7 @@ namespace Recyclable.Collections if (requiredBlockCount > (list._memoryBlocks?.Length ?? 0)) { // Allocate new memory block for all arrays - newMemoryBlocks = requiredBlockCount >= RecyclableDefaults.MinPooledArrayLength ? list._memoryBlocksPool.Rent(requiredBlockCount) : new T[requiredBlockCount][]; + newMemoryBlocks = requiredBlockCount >= RecyclableDefaults.MinPooledArrayLength ? RecyclableArrayPool.RentShared(requiredBlockCount) : new T[requiredBlockCount][]; // Copy arrays from the old memory block for all arrays if (sourceBlockCount > 0) @@ -195,7 +175,7 @@ namespace Recyclable.Collections // We can now return the old memory block for all arrays itself if (sourceBlockCount >= RecyclableDefaults.MinPooledArrayLength) { - list._memoryBlocksPool.Return(list._memoryBlocks!, NeedsClearing); + RecyclableArrayPool.ReturnShared(list._memoryBlocks!, NeedsClearing); } } @@ -212,9 +192,10 @@ namespace Recyclable.Collections if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) { blockIndex = sourceBlockCount; + ref OneSizeArrayPool blockArrayPool = ref RecyclableArrayPool.Shared(minBlockSize); while (true) { - newMemoryBlocks[blockIndex] = blockArrayPool.Rent(minBlockSize); + newMemoryBlocks[blockIndex] = blockArrayPool.Rent(); if (blockIndex + 1 < requiredBlockCount) { blockIndex++; @@ -248,13 +229,10 @@ namespace Recyclable.Collections } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public static void SetupBlockArrayPooling(RecyclableLongList list, int blockSize, ArrayPool? blockArrayPool = null) + public static void SetupBlockArrayPooling(RecyclableLongList list, int blockSize) { list._blockSize = blockSize; list._blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); - list._blockArrayPool = blockSize >= RecyclableDefaults.MinPooledArrayLength - ? blockArrayPool ?? RecyclableArrayPool.Shared(blockSize) - : RecyclableArrayPool.Null; } #pragma warning disable CA2208 diff --git a/Recyclable.Collections/RecyclableLongList.cs b/Recyclable.Collections/RecyclableLongList.cs index 361393b..6155546 100644 --- a/Recyclable.Collections/RecyclableLongList.cs +++ b/Recyclable.Collections/RecyclableLongList.cs @@ -1,7 +1,7 @@ -using System.Buffers; -using System.Collections; +using System.Collections; using System.Numerics; using System.Runtime.CompilerServices; +using Recyclable.Collections.Pools; [assembly: InternalsVisibleTo("Recyclable.Collections.Benchmarks")] [assembly: InternalsVisibleTo("Recyclable.CollectionsTests")] @@ -10,8 +10,6 @@ namespace Recyclable.Collections { public partial class RecyclableLongList : IList, IReadOnlyList, IDisposable { - private static readonly ArrayPool _defaultMemoryBlocksPool = ArrayPool.Create(); - private static readonly ArrayPool _defaultBlockArrayPool = ArrayPool.Create(); #pragma warning disable CA1825 private static readonly T[][] _emptyMemoryBlocksArray = new T[0][]; private static readonly T[] _emptyBlockArray = new T[0]; @@ -26,8 +24,6 @@ namespace Recyclable.Collections internal int _nextItemIndex; private int _reservedBlockCount; - private readonly ArrayPool _memoryBlocksPool; - private ArrayPool _blockArrayPool; #nullable disable internal T[][] _memoryBlocks; #nullable restore @@ -42,7 +38,7 @@ namespace Recyclable.Collections _version++; if (_capacity != value) { - _capacity = Helpers.Resize(this, _blockSize, _blockSizePow2BitShift, value); + _capacity = Helpers.Resize(this, _blockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)value))); _version++; } } @@ -87,7 +83,7 @@ namespace Recyclable.Collections { if (copied + growByCount > capacity) { - capacity = Helpers.EnsureCapacity(this, capacity + growByCount); + capacity = Helpers.Resize(this, _blockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)(capacity + growByCount)))); memoryBlocksCount = _reservedBlockCount; if (blockSize != _blockSize) { @@ -118,6 +114,7 @@ namespace Recyclable.Collections _nextItemIndex = 0; } + _capacity = capacity; return; } @@ -151,7 +148,7 @@ namespace Recyclable.Collections long requiredCapacity = copied + requiredAdditionalCapacity; if (_capacity < requiredCapacity) { - _ = Helpers.EnsureCapacity(this, requiredCapacity); + _capacity = Helpers.Resize(this, _blockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)requiredCapacity))); blockSize = _blockSize; } @@ -182,57 +179,40 @@ namespace Recyclable.Collections [MethodImpl(MethodImplOptions.AggressiveOptimization)] #pragma warning disable CS8618 // The reported properties / fields are set by SetupBlockArrayPooling - public RecyclableLongList(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + public RecyclableLongList(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) #pragma warning restore CS8618 { - _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); if (expectedItemsCount > 0) { - minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); - Helpers.SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); - _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value); - if (_blockSize != _memoryBlocks![0].Length && _capacity > 0) - { - Helpers.SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); - _blockSizeMinus1 = _blockSize - 1; - } - else - { - _blockSizeMinus1 = minBlockSize - 1; - } + Helpers.SetupBlockArrayPooling(this, minBlockSize); + _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)expectedItemsCount.Value))); + _blockSizeMinus1 = minBlockSize - 1; } else { - Helpers.SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + Helpers.SetupBlockArrayPooling(this, minBlockSize); _blockSizeMinus1 = _blockSize - 1; _memoryBlocks = _emptyMemoryBlocksArray; } } #pragma warning disable CS8618 // The reported properties / fields are set by SetupBlockArrayPooling - public RecyclableLongList(in T[] source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + public RecyclableLongList(in T[] source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) #pragma warning restore CS8618 { - _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + if (expectedItemsCount > 0) { - minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); - Helpers.SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); - _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value); - if (minBlockSize != _memoryBlocks![0].Length && _capacity > 0) - { - Helpers.SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); - _blockSizeMinus1 = _blockSize - 1; - } - else - { - _blockSizeMinus1 = minBlockSize - 1; - } + Helpers.SetupBlockArrayPooling(this, minBlockSize); + _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)expectedItemsCount.Value))); + _blockSizeMinus1 = minBlockSize - 1; } else { - Helpers.SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + Helpers.SetupBlockArrayPooling(this, minBlockSize); _blockSizeMinus1 = _blockSize - 1; _memoryBlocks = _emptyMemoryBlocksArray; } @@ -241,28 +221,20 @@ namespace Recyclable.Collections } #pragma warning disable CS8618 // The reported properties / fields are set by SetupBlockArrayPooling - public RecyclableLongList(ReadOnlySpan source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + public RecyclableLongList(ReadOnlySpan source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) #pragma warning restore CS8618 { - _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + if (expectedItemsCount > 0) { - minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); - Helpers.SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); - _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value); - if (minBlockSize != _memoryBlocks![0].Length && _capacity > 0) - { - Helpers.SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); - _blockSizeMinus1 = _blockSize - 1; - } - else - { - _blockSizeMinus1 = minBlockSize - 1; - } + Helpers.SetupBlockArrayPooling(this, minBlockSize); + _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)expectedItemsCount.Value))); + _blockSizeMinus1 = minBlockSize - 1; } else { - Helpers.SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + Helpers.SetupBlockArrayPooling(this, minBlockSize); _blockSizeMinus1 = _blockSize - 1; _memoryBlocks = _emptyMemoryBlocksArray; } @@ -271,28 +243,20 @@ namespace Recyclable.Collections } #pragma warning disable CS8618 // The reported properties / fields are set by SetupBlockArrayPooling - public RecyclableLongList(RecyclableList source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + public RecyclableLongList(RecyclableList source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) #pragma warning restore CS8618 { - _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + if (expectedItemsCount > 0) { - minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); - Helpers.SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); - _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value); - if (minBlockSize != _memoryBlocks![0].Length && _capacity > 0) - { - Helpers.SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); - _blockSizeMinus1 = _blockSize - 1; - } - else - { - _blockSizeMinus1 = minBlockSize - 1; - } + Helpers.SetupBlockArrayPooling(this, minBlockSize); + _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)expectedItemsCount.Value))); + _blockSizeMinus1 = minBlockSize - 1; } else { - Helpers.SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + Helpers.SetupBlockArrayPooling(this, minBlockSize); _blockSizeMinus1 = _blockSize - 1; _memoryBlocks = _emptyMemoryBlocksArray; } @@ -301,28 +265,20 @@ namespace Recyclable.Collections } #pragma warning disable CS8618 // The reported properties / fields are set by SetupBlockArrayPooling - public RecyclableLongList(RecyclableLongList source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + public RecyclableLongList(RecyclableLongList source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) #pragma warning restore CS8618 { - _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + if (expectedItemsCount > 0) { - minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); - Helpers.SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); - _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value); - if (minBlockSize != _memoryBlocks![0].Length && _capacity > 0) - { - Helpers.SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); - _blockSizeMinus1 = _blockSize - 1; - } - else - { - _blockSizeMinus1 = minBlockSize - 1; - } + Helpers.SetupBlockArrayPooling(this, minBlockSize); + _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)expectedItemsCount.Value))); + _blockSizeMinus1 = minBlockSize - 1; } else { - Helpers.SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + Helpers.SetupBlockArrayPooling(this, minBlockSize); _blockSizeMinus1 = _blockSize - 1; _memoryBlocks = _emptyMemoryBlocksArray; } @@ -331,28 +287,20 @@ namespace Recyclable.Collections } #pragma warning disable CS8618 // The reported properties / fields are set by SetupBlockArrayPooling - public RecyclableLongList(List source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + public RecyclableLongList(List source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) #pragma warning restore CS8618 { - _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + if (expectedItemsCount > 0) { - minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); - Helpers.SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); - _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value); - if (minBlockSize != _memoryBlocks![0].Length && _capacity > 0) - { - Helpers.SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); - _blockSizeMinus1 = _blockSize - 1; - } - else - { - _blockSizeMinus1 = minBlockSize - 1; - } + Helpers.SetupBlockArrayPooling(this, minBlockSize); + _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)expectedItemsCount.Value))); + _blockSizeMinus1 = minBlockSize - 1; } else { - Helpers.SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + Helpers.SetupBlockArrayPooling(this, minBlockSize); _blockSizeMinus1 = _blockSize - 1; _memoryBlocks = _emptyMemoryBlocksArray; } @@ -361,28 +309,20 @@ namespace Recyclable.Collections } #pragma warning disable CS8618 // The reported properties / fields are set by SetupBlockArrayPooling - public RecyclableLongList(IList source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + public RecyclableLongList(IList source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) #pragma warning restore CS8618 { - _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + if (expectedItemsCount > 0) { - minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); - Helpers.SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); - _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value); - if (minBlockSize != _memoryBlocks![0].Length && _capacity > 0) - { - Helpers.SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); - _blockSizeMinus1 = _blockSize - 1; - } - else - { - _blockSizeMinus1 = minBlockSize - 1; - } + Helpers.SetupBlockArrayPooling(this, minBlockSize); + _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)expectedItemsCount.Value))); + _blockSizeMinus1 = minBlockSize - 1; } else { - Helpers.SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + Helpers.SetupBlockArrayPooling(this, minBlockSize); _blockSizeMinus1 = _blockSize - 1; _memoryBlocks = _emptyMemoryBlocksArray; } @@ -391,28 +331,20 @@ namespace Recyclable.Collections } #pragma warning disable CS8618 // The reported properties / fields are set by SetupBlockArrayPooling - public RecyclableLongList(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + public RecyclableLongList(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default) #pragma warning restore CS8618 { - _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + if (expectedItemsCount > 0) { - minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); - Helpers.SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); - _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value); - if (minBlockSize != _memoryBlocks![0].Length && _capacity > 0) - { - Helpers.SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); - _blockSizeMinus1 = _blockSize - 1; - } - else - { - _blockSizeMinus1 = minBlockSize - 1; - } + Helpers.SetupBlockArrayPooling(this, minBlockSize); + _capacity = Helpers.Resize(this, minBlockSize, _blockSizePow2BitShift, checked((int)BitOperations.RoundUpToPowerOf2((uint)expectedItemsCount.Value))); + _blockSizeMinus1 = minBlockSize - 1; } else { - Helpers.SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + Helpers.SetupBlockArrayPooling(this, minBlockSize); _blockSizeMinus1 = _blockSize - 1; _memoryBlocks = _emptyMemoryBlocksArray; } @@ -447,7 +379,7 @@ namespace Recyclable.Collections { if (_capacity < _longCount + 1) { - _ = Helpers.EnsureCapacity(this, _longCount + 1); + _capacity = Helpers.Resize(this, _blockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)(_longCount + 1)))); } _memoryBlocks[_nextItemBlockIndex][_nextItemIndex++] = item; @@ -480,7 +412,7 @@ namespace Recyclable.Collections long targetCapacity = _longCount + items.LongLength; if (_capacity < targetCapacity) { - _ = Helpers.EnsureCapacity(this, targetCapacity); + _capacity = Helpers.Resize(this, _blockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity))); } int blockSize = _blockSize, @@ -529,7 +461,7 @@ namespace Recyclable.Collections long targetCapacity = _longCount + items.Count; if (_capacity < targetCapacity) { - _ = Helpers.EnsureCapacity(this, targetCapacity); + _capacity = Helpers.Resize(this, _blockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity))); } int blockSize = _blockSize, @@ -570,7 +502,7 @@ namespace Recyclable.Collections long targetCapacity = _longCount + itemsSpan.Length; if (_capacity < targetCapacity) { - _ = Helpers.EnsureCapacity(this, targetCapacity); + _capacity = Helpers.Resize(this, _blockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity))); } int blockSize = _blockSize, @@ -620,7 +552,7 @@ namespace Recyclable.Collections if (_capacity < targetCapacity) { - _ = Helpers.EnsureCapacity(this, targetCapacity); + _capacity = Helpers.Resize(this, _blockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity))); } int blockSize = _blockSize, @@ -691,7 +623,7 @@ namespace Recyclable.Collections long targetCapacity = _longCount + items.Count; if (_capacity < targetCapacity) { - _ = Helpers.EnsureCapacity(this, targetCapacity); + _capacity = Helpers.Resize(this, _blockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity))); } int blockSize = _blockSize, @@ -739,7 +671,7 @@ namespace Recyclable.Collections long targetCapacity = _longCount + items.Count; if (_capacity < targetCapacity) { - _ = Helpers.EnsureCapacity(this, targetCapacity); + _capacity = Helpers.Resize(this, _blockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity))); } int blockSize = _blockSize, @@ -748,7 +680,7 @@ namespace Recyclable.Collections // We're better off to temporarily copy it to a fixed array, // than copying them item by item. We may run into OOMs, though. - T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? _defaultBlockArrayPool.Rent(items.Count) : new T[items.Count]; + T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? RecyclableArrayPool.RentShared((int)BitOperations.RoundUpToPowerOf2((uint)items.Count)) : new T[items.Count]; try { items.CopyTo(itemsBuffer, 0); @@ -779,7 +711,7 @@ namespace Recyclable.Collections { if (items.Count >= RecyclableDefaults.MinPooledArrayLength) { - _defaultBlockArrayPool.Return(itemsBuffer, NeedsClearing); + RecyclableArrayPool.ReturnShared(itemsBuffer, NeedsClearing); } _version++; @@ -831,11 +763,10 @@ namespace Recyclable.Collections { T[][] memoryBlocks = _memoryBlocks; int memoryBlocksCount = _reservedBlockCount; - ArrayPool blockArrayPool = _blockArrayPool; int toRemoveIdx = 0; while (toRemoveIdx < memoryBlocksCount) { - blockArrayPool.Return(memoryBlocks[toRemoveIdx++], NeedsClearing); + RecyclableArrayPool.ReturnShared(memoryBlocks[toRemoveIdx++], NeedsClearing); } } @@ -999,7 +930,7 @@ namespace Recyclable.Collections { if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) { - _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); + RecyclableArrayPool.ReturnShared(_memoryBlocks[index], NeedsClearing); _memoryBlocks[index] = _emptyBlockArray; } else @@ -1108,7 +1039,7 @@ namespace Recyclable.Collections if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength) { // If anything, it has been already cleared by .Clear(), Remove() or RemoveAt() methods, as the list was modified / disposed. - _memoryBlocksPool.Return(_memoryBlocks, false); + RecyclableArrayPool.ReturnShared(_memoryBlocks, false); } } diff --git a/Recyclable.Collections/ThrowHelper.cs b/Recyclable.Collections/ThrowHelper.cs index 8779ce6..8350947 100644 --- a/Recyclable.Collections/ThrowHelper.cs +++ b/Recyclable.Collections/ThrowHelper.cs @@ -13,5 +13,10 @@ namespace Recyclable.Collections Contract.EnsuresOnThrow(false); throw new ArgumentOutOfRangeException(argumentName, message); } + + public static void ThrowWrongReturnedArraySizeException(int returnedLength, int blockSize) + { + throw new InvalidOperationException($"The array size {returnedLength} doesn't match the size {blockSize} of the pool buffer and cannot be returned to this pool."); + } } }