From 9609b77a52e290cdeb843723de76ff9f65e45fb9 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Wed, 12 Apr 2023 23:39:25 +0200 Subject: [PATCH] Additional benchmarks added. --- ...clableListPoc.Benchmarks.EnsureCapacity.cs | 2633 +++++++++++------ .../POC/RecyclableListPoc.Benchmarks.cs | 6 +- 2 files changed, 1812 insertions(+), 827 deletions(-) diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs index c0efe8b..b160456 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs @@ -6,882 +6,1858 @@ using System.Runtime.CompilerServices; namespace Recyclable.Collections.Benchmarks.POC { - public partial class RecyclableListPocBenchmarks + #region RecyclableList v1 + + internal class RecyclableListV1 : IDisposable, IList { - #region Old class + private const int ItemNotFoundIndex = -1; - private class OldRecyclableList : IDisposable, IList + private static readonly ArrayPool _defaultMemoryBlocksPool = ArrayPool.Create(); + private static readonly ArrayPool _defaultBlockArrayPool = ArrayPool.Create(); + private static readonly T[][] _emptyMemoryBlocksArray = new T[0][]; + private static readonly T[] _emptyBlockArray = new T[0]; + + private int _blockSize; + private int _blockSizePow2Shift; + private int _nextItemBlockIndex; + private int _nextItemIndex; + + protected static readonly bool NeedsClearing = !typeof(T).IsValueType; + + protected ArrayPool _memoryBlocksPool; + protected ArrayPool _blockArrayPool; + protected T[][] _memoryBlocks; + + private long _capacity; + public long Capacity { - private const int ItemNotFoundIndex = -1; + get => _capacity; + protected set => _capacity = value; + } - private static readonly ArrayPool _defaultMemoryBlocksPool = ArrayPool.Create(); - private static readonly ArrayPool _defaultBlockArrayPool = ArrayPool.Create(); - private static readonly T[][] _emptyMemoryBlocksArray = new T[0][]; - private static readonly T[] _emptyBlockArray = new T[0]; + public int Count => checked((int)_longCount); + public bool IsReadOnly { get; } + public int LastTakenBlockIndex => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1); - private int _blockSize; - private int _blockSizePow2Shift; - private int _nextItemBlockIndex; - private int _nextItemIndex; + protected long _longCount; - protected static readonly bool NeedsClearing = !typeof(T).IsValueType; + public long LongCount + { + get => _longCount; + set => _longCount = value; + } - protected ArrayPool _memoryBlocksPool; - protected ArrayPool _blockArrayPool; - protected T[][] _memoryBlocks; + public int ReservedBlockCount => (int)(_capacity >> _blockSizePow2Shift) + ((_capacity & (_blockSize - 1)) > 0 ? 1 : 0); + public int BlockSize => _blockSize; + public int NextItemBlockIndex => _nextItemBlockIndex; + public int NextItemIndex => _nextItemIndex; - private long _capacity; - public long Capacity + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private void DoRemoveAt(long index) + { + int blockSize = _blockSize; + int nextItemIndex = _nextItemIndex; + T[][] memoryBlocks = _memoryBlocks; + _longCount--; + if (index < _longCount) { - get => _capacity; - protected set => _capacity = value; - } + int sourceBlockIndex = (int)((index + 1) >> _blockSizePow2Shift); + int sourceItemIndex = (int)((index + 1) & (blockSize - 1)); - public int Count => checked((int)_longCount); - public bool IsReadOnly { get; } - public int LastTakenBlockIndex => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1); + int targetBlockIndex = (int)(index >> _blockSizePow2Shift); + int targetItemIndex = (int)(index & (blockSize - 1)); - protected long _longCount; - - public long LongCount - { - get => _longCount; - set => _longCount = value; - } - - public int ReservedBlockCount => (int)(_capacity >> _blockSizePow2Shift) + ((_capacity & (_blockSize - 1)) > 0 ? 1 : 0); - public int BlockSize => _blockSize; - public int NextItemBlockIndex => _nextItemBlockIndex; - public int NextItemIndex => _nextItemIndex; - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private void DoRemoveAt(long index) - { - int blockSize = _blockSize; - int nextItemIndex = _nextItemIndex; - T[][] memoryBlocks = _memoryBlocks; - _longCount--; - if (index < _longCount) + int lastTakenBlockIndex = LastTakenBlockIndex; + while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != _nextItemBlockIndex))) { - int sourceBlockIndex = (int)((index + 1) >> _blockSizePow2Shift); - int sourceItemIndex = (int)((index + 1) & (blockSize - 1)); + int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0 + ? Math.Min(blockSize - sourceItemIndex, blockSize - targetItemIndex) + : Math.Min(nextItemIndex, blockSize - targetItemIndex); - int targetBlockIndex = (int)(index >> _blockSizePow2Shift); - int targetItemIndex = (int)(index & (blockSize - 1)); - - int lastTakenBlockIndex = LastTakenBlockIndex; - while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != _nextItemBlockIndex))) + Array.Copy(memoryBlocks[sourceBlockIndex], sourceItemIndex, memoryBlocks[targetBlockIndex], targetItemIndex, toCopy); + // We didn't have enough room in the target array block. There are still items in the source array block to copy. + if (sourceItemIndex + toCopy < blockSize) { - int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0 - ? Math.Min(blockSize - sourceItemIndex, blockSize - targetItemIndex) - : Math.Min(nextItemIndex, blockSize - targetItemIndex); - - Array.Copy(memoryBlocks[sourceBlockIndex], sourceItemIndex, memoryBlocks[targetBlockIndex], targetItemIndex, toCopy); - // We didn't have enough room in the target array block. There are still items in the source array block to copy. - if (sourceItemIndex + toCopy < blockSize) + sourceItemIndex += toCopy; + targetBlockIndex++; + targetItemIndex = 0; + } + // We copied all the source items in the current array block. But have we filled the target? + else + { + sourceItemIndex = 0; + sourceBlockIndex++; + if (targetItemIndex + toCopy < blockSize) + { + targetItemIndex += toCopy; + } + else { - sourceItemIndex += toCopy; targetBlockIndex++; targetItemIndex = 0; } - // We copied all the source items in the current array block. But have we filled the target? + } + } + } + + if (nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = blockSize - 1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + new Span(memoryBlocks[_nextItemBlockIndex], _nextItemIndex, 1)[0] = default; +#pragma warning restore CS8601 + } + } + + private static void ThrowArgumentOutOfRangeException(in string message) => throw new ArgumentOutOfRangeException("index", message); + + protected void AddRangeEnumerated(IEnumerable source, int growByCount) + { + long i, copied = _longCount; + + int blockSize = _blockSize, targetItemIdx = _nextItemIndex, targetBlockIdx = _nextItemBlockIndex; + + using IEnumerator enumerator = source.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return; + } + + long capacity = _capacity; + long available = capacity - copied; + + Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); + Span blockArraySpan; + var memoryBlocksCount = memoryBlocksSpan.Length; + while (true) + { + if (copied + growByCount > capacity) + { + capacity = EnsureCapacity(capacity + growByCount); + memoryBlocksCount = _memoryBlocks.Length; + if (blockSize != _blockSize) + { + blockSize = _blockSize; + } + + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + available = capacity - copied; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + for (i = 1; i <= available; i++) + { + blockArraySpan[targetItemIdx++] = enumerator.Current; + + if (!enumerator.MoveNext()) + { + if (targetItemIdx < blockSize) + { + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + } else { - sourceItemIndex = 0; - sourceBlockIndex++; - if (targetItemIndex + toCopy < blockSize) - { - targetItemIndex += toCopy; - } - else - { - targetBlockIndex++; - targetItemIndex = 0; - } + _nextItemBlockIndex = targetBlockIdx + 1; + _nextItemIndex = 0; } + + _longCount += copied + i - _longCount; + return; + } + + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); } } - if (nextItemIndex > 0) + copied += available; + } + } + + protected void AddRangeWithKnownCount(IEnumerable source, int requiredAdditionalCapacity) + { + long copied = _longCount; + int blockSize = _blockSize; + int targetItemIdx = _nextItemIndex; + int targetBlockIdx = _nextItemBlockIndex; + + Span memoryBlocksSpan; + Span blockArraySpan; + long requiredCapacity = copied + requiredAdditionalCapacity; + if (_capacity < requiredCapacity) + { + _ = EnsureCapacity(requiredCapacity); + blockSize = _blockSize; + } + + var memoryBlocksCount = ReservedBlockCount; + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + foreach (var item in source) + { + blockArraySpan[targetItemIdx++] = item; + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + _longCount = requiredCapacity; + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + return; + } + + protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) + { + Span memoryBlocksSpan = new(memoryBlocks, 0, lastBlockIndex + 1); + int itemIndex, blockIndex; + for (blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); + if (itemIndex >= 0) + { + return itemIndex + (blockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))); + } + } + + if (blockIndex == lastBlockIndex) + { + itemIndex = nextItemIndex != 0 + ? Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, nextItemIndex) + : Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, blockSize); + } + else + { + return ItemNotFoundIndex; + } + + return itemIndex >= 0 ? itemIndex + (lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndex; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected static long Resize(ref T[][]? source, int minBlockSize, long oldCapacity, long newCapacity, ArrayPool memoryBlocksPool, ArrayPool blockArrayPool) + { + int minBlockSizePowerToShift = 31 - BitOperations.LeadingZeroCount((uint)minBlockSize); + int sourceBlockCount = (int)(oldCapacity >> minBlockSizePowerToShift); + int requiredBlockCount = (int)(newCapacity >> minBlockSizePowerToShift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0); + int blockIndex; + T[][]? newMemoryBlocks; + Span memoryBlocksSpan; + + // Release excessive blocks if we're downsizing + if (requiredBlockCount < sourceBlockCount && (minBlockSize >= RecyclableDefaults.MinPooledArrayLength)) + { + memoryBlocksSpan = new(source, requiredBlockCount, sourceBlockCount - requiredBlockCount); + blockIndex = 0; + while (blockIndex < memoryBlocksSpan.Length) + { + blockArrayPool.Return(memoryBlocksSpan[blockIndex++]!, NeedsClearing); + } + } + + // Allocate new memory block for all arrays + newMemoryBlocks = requiredBlockCount < RecyclableDefaults.MinPooledArrayLength + ? (new T[requiredBlockCount][]) + : memoryBlocksPool.Rent(requiredBlockCount); + + // Copy arrays from the old memory block for all arrays + if (sourceBlockCount > 0) + { + Array.Copy(source!, newMemoryBlocks, sourceBlockCount); + // We can now return the old memory block for all arrays itself + if (sourceBlockCount >= RecyclableDefaults.MinPooledArrayLength) + { + memoryBlocksPool.Return(source!, true); + } + } + + source = newMemoryBlocks; + + // Allocate arrays for any new blocks + if (requiredBlockCount > sourceBlockCount) + { + memoryBlocksSpan = new(newMemoryBlocks, sourceBlockCount, newMemoryBlocks.Length - sourceBlockCount); + if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) + { + if (sourceBlockCount == 0) + { + memoryBlocksSpan[0] = blockArrayPool.Rent(minBlockSize); + minBlockSize = memoryBlocksSpan[0].Length; + blockIndex = 1; + } + else + { + blockIndex = 0; + } + + while (blockIndex < memoryBlocksSpan.Length) + { + memoryBlocksSpan[blockIndex++] = blockArrayPool.Rent(minBlockSize); + } + } + else + { + blockIndex = 0; + while (blockIndex < memoryBlocksSpan.Length) + { + memoryBlocksSpan[blockIndex++] = new T[minBlockSize]; + } + } + } + + return newMemoryBlocks.Length << minBlockSizePowerToShift; + } + + internal long EnsureCapacity(long requestedCapacity) + { + long newCapacity = _capacity > 0 + ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) + : requestedCapacity; + + newCapacity = Resize(ref _memoryBlocks!, _blockSize, _capacity, newCapacity, _memoryBlocksPool, _blockArrayPool); + if (newCapacity > 0 && _capacity == 0) + { + _blockSize = _memoryBlocks[0].Length; + _blockSizePow2Shift = MathUtils.GetPow2Shift(_blockSize); + } + + _capacity = newCapacity; + return newCapacity; + } + + public RecyclableListV1(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + { + memoryBlocksPool ??= _defaultMemoryBlocksPool; + blockArrayPool ??= _defaultBlockArrayPool; + _memoryBlocksPool = memoryBlocksPool; + _blockArrayPool = blockArrayPool; + + if (expectedItemsCount > 0) + { + _capacity = Resize(ref _memoryBlocks!, (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize), 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); + if (_memoryBlocks.Length > 0) + { + minBlockSize = _memoryBlocks[0].Length; + } + + _blockSize = minBlockSize; + _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); + } + else + { + _blockSize = (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize); + _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); + _memoryBlocks = _emptyMemoryBlocksArray; + } + } + + public RecyclableListV1(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + { + memoryBlocksPool ??= _defaultMemoryBlocksPool; + blockArrayPool ??= _defaultBlockArrayPool; + _memoryBlocksPool = memoryBlocksPool; + _blockArrayPool = blockArrayPool; + + if (expectedItemsCount > 0) + { + _capacity = Resize(ref _memoryBlocks!, (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize), 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); + if (_memoryBlocks.Length > 0) + { + minBlockSize = _memoryBlocks[0].Length; + } + + _blockSize = minBlockSize; + _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); + } + else + { + _blockSize = (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize); + _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); + _memoryBlocks = _emptyMemoryBlocksArray; + } + + AddRange(source); + } + + public T this[long index] + { + get => _memoryBlocks[index >> _blockSizePow2Shift][index & (_blockSize - 1)]; + set => new Span(_memoryBlocks[(int)(index >> _blockSizePow2Shift)])[(int)(index & (_blockSize - 1))] = value; + } + + public T this[int index] + { + get => _memoryBlocks[index >> _blockSizePow2Shift][index & (_blockSize - 1)]; + set => new Span(_memoryBlocks[index >> _blockSizePow2Shift])[index & (_blockSize - 1)] = value; + } + + public T[][] MemoryBlocks { get => _memoryBlocks; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(T item) + { + if (_capacity < _longCount + 1) + { + _ = EnsureCapacity(_longCount + 1); + } + + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex++] = item; + if (_nextItemIndex == _blockSize) + { + _nextItemBlockIndex++; + _nextItemIndex = 0; + } + + _longCount++; + } + + public void AddRange(in T[] items) + { + if (items.LongLength == 0) + { + return; + } + + long targetCapacity = _longCount + items.LongLength; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _memoryBlocks.Length; + + Span itemsSpan = new(items); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + if (itemsSpan.Length > 0) + { + itemsSpan.CopyTo(targetBlockArraySpan); + } + + _nextItemIndex = itemsSpan.Length; + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableArrayList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _memoryBlocks.Length; + + Span itemsSpan = new(items.AsArray(), 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + _nextItemIndex = itemsSpan.Length; + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableListV1 items) + { + if (items.LongCount == 0) + { + return; + } + + long itemsCount = items.LongCount, + targetCapacity = _longCount + itemsCount; + + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + int blockSize = _blockSize, + sourceBlockIndex = 0, + targetBlockIndex = _nextItemBlockIndex, + toCopy; + + Span sourceMemoryBlocksSpan = new(items._memoryBlocks, 0, items._memoryBlocks.Length); + Span itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + Span targetMemoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); + Span targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + long copiedCount = 0; + while (copiedCount < itemsCount) + { + toCopy = (int)Math.Min(itemsCount - copiedCount, itemsSpan.Length); + if (targetBlockArraySpan.Length < toCopy) + { + toCopy = targetBlockArraySpan.Length; + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + itemsSpan = itemsSpan[toCopy..]; + copiedCount += toCopy; + } + else + { + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockArraySpan = targetBlockArraySpan[toCopy..]; + itemsSpan = itemsSpan[toCopy..]; + if (itemsSpan.IsEmpty && sourceBlockIndex + 1 < sourceMemoryBlocksSpan.Length) + { + sourceBlockIndex++; + itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + } + + copiedCount += toCopy; + if (targetBlockArraySpan.IsEmpty && copiedCount < itemsCount) + { + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + } + } + + if ((targetCapacity & (blockSize - 1)) == 0) + { + _nextItemIndex = 0; + _nextItemBlockIndex = targetBlockIndex + 1; + } + else + { + _nextItemIndex = (int)(targetCapacity & (blockSize - 1)); + _nextItemBlockIndex = targetBlockIndex; + + } + + _longCount = targetCapacity; + } + + public void AddRange(List items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + itemsCount = items.Count, + copied = Math.Min(blockSize - _nextItemIndex, itemsCount); + + Span memoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); + items.CopyTo(0, memoryBlocksSpan[targetBlockIndex], _nextItemIndex, copied); + if (_nextItemIndex + copied == blockSize) + { + targetBlockIndex++; + _nextItemIndex = 0; + } + else + { + _nextItemIndex += copied; + } + + while (blockSize <= itemsCount - copied) + { + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex++], 0, blockSize); + copied += blockSize; + } + + if ((itemsCount - copied < blockSize) && (itemsCount - copied != 0)) + { + _nextItemIndex = itemsCount - copied; + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex], 0, _nextItemIndex); + } + + _nextItemBlockIndex = targetBlockIndex; + _longCount = targetCapacity; + } + + public void AddRange(IList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _memoryBlocks.Length; + + T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? _blockArrayPool.Rent(items.Count) : new T[items.Count]; + try + { + items.CopyTo(itemsBuffer, 0); + + Span itemsSpan = new(itemsBuffer, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + _nextItemIndex = itemsSpan.Length; + } + finally + { + if (items.Count >= RecyclableDefaults.MinPooledArrayLength) + { + _blockArrayPool.Return(itemsBuffer, NeedsClearing); + } + } + } + + public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) + { + if (source is RecyclableListV1 sourceRecyclableList) + { + AddRange(sourceRecyclableList); + return; + } + + if (source is RecyclableArrayList sourceRecyclableArrayList) + { + AddRange(sourceRecyclableArrayList); + return; + } + + if (source is T[] sourceArray) + { + AddRange(sourceArray); + return; + } + + if (source is List sourceList) + { + AddRange(sourceList); + return; + } + + if (source is IList sourceIList) + { + AddRange(sourceIList); + return; + } + + if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) + { + AddRangeWithKnownCount(source, requiredAdditionalCapacity); + return; + } + + AddRangeEnumerated(source, growByCount); + } + + public void Clear() + { + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); + int memoryBlocksCount = memoryBlocksSpan.Length; + ArrayPool blockArrayPool = _blockArrayPool; + for (int toRemoveIdx = 0; toRemoveIdx < memoryBlocksCount; toRemoveIdx++) + { + blockArrayPool.Return(memoryBlocksSpan[toRemoveIdx], NeedsClearing); + } + } + + _capacity = 0; + _nextItemBlockIndex = 0; + _nextItemIndex = 0; + _longCount = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(T item) + { + if (_longCount == 0) + { + return false; + } + + if (_nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0)) + { + return Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) >= 0; + } + + Span memoryBlocksSpan = new(_memoryBlocks); + int lastBlockIndex = _nextItemBlockIndex; + for (int blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + if (Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _blockSize) >= 0) + { + return true; + } + } + + return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, _nextItemIndex) >= 0; + } + + public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, arrayIndex); + + public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndex + : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) + ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) + : (int)DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex); + + public void Insert(int index, T item) => throw new NotSupportedException(); + public long LongIndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndex + : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) + ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) + : DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex); + + public bool Remove(T item) + { + var itemIndex = LongIndexOf(item); + if (itemIndex >= 0) + { + DoRemoveAt(itemIndex); + return true; + } + + return false; + } + + public void RemoveBlock(int index) + { + if (_blockSize < RecyclableDefaults.MinPooledArrayLength) + { + _memoryBlocks[index] = _emptyBlockArray; + } + else + { + _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); + } + + _capacity -= _blockSize; + if (_nextItemBlockIndex == index) + { + _nextItemIndex = 0; + } + else if (_nextItemBlockIndex > index) + { + _nextItemBlockIndex--; + } + } + + public void RemoveAt(int index) + { + if (index > _longCount - 1 || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + DoRemoveAt(index); + } + + public void RemoveAt(long index) + { + if (index > _longCount - 1 || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + DoRemoveAt(index); + } + + IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + ~RecyclableListV1() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + if (_capacity > 0) + { + Clear(); + if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength) + { + _memoryBlocksPool.Return(_memoryBlocks, NeedsClearing); + } + + GC.SuppressFinalize(this); + } + } + } + + + #endregion + + #region RecyclableList v2 + + internal class RecyclableListV2 : IDisposable, IList + { + private const int ItemNotFoundIndex = -1; + + private static readonly ArrayPool _defaultMemoryBlocksPool = ArrayPool.Create(); + private static readonly ArrayPool _defaultBlockArrayPool = ArrayPool.Create(); + private static readonly T[][] _emptyMemoryBlocksArray = new T[0][]; + private static readonly T[] _emptyBlockArray = new T[0]; + + private int _blockSize; + private byte _blockSizePow2Shift; + private int _nextItemBlockIndex; + private int _nextItemIndex; + + protected static readonly bool NeedsClearing = !typeof(T).IsValueType; + + protected ArrayPool _memoryBlocksPool; + protected ArrayPool _blockArrayPool; + protected T[][] _memoryBlocks; + + private long _capacity; + public long Capacity + { + get => _capacity; + protected set => _capacity = value; + } + + public int Count => checked((int)_longCount); + public bool IsReadOnly { get; } + public int LastTakenBlockIndex => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1); + + protected long _longCount; + + public long LongCount + { + get => _longCount; + set => _longCount = value; + } + + public int ReservedBlockCount => checked((int)(_capacity >> _blockSizePow2Shift) + ((_capacity & (_blockSize - 1)) > 0 ? 1 : 0)); + public int BlockSize => _blockSize; + public int NextItemBlockIndex => _nextItemBlockIndex; + public int NextItemIndex => _nextItemIndex; + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + private static void CopyItems(RecyclableListV2 list, long index) + { + T[][] memoryBlocks = list._memoryBlocks; + int nextItemIndex = list._nextItemIndex; + int blockSize = list._blockSize; + int sourceBlockIndex = checked((int)((index + 1) >> list._blockSizePow2Shift)); + int sourceItemIndex = checked((int)((index + 1) & (blockSize - 1))); + + int targetBlockIndex = checked((int)(index >> list._blockSizePow2Shift)); + int targetItemIndex = checked((int)(index & (blockSize - 1))); + + int lastTakenBlockIndex = list.LastTakenBlockIndex; + while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != list._nextItemBlockIndex))) + { + int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0 + ? blockSize - (sourceItemIndex >= targetItemIndex ? sourceItemIndex : targetItemIndex) + : Math.Min(nextItemIndex, blockSize - targetItemIndex); + + Array.Copy(memoryBlocks[sourceBlockIndex], sourceItemIndex, memoryBlocks[targetBlockIndex], targetItemIndex, toCopy); + + // We didn't have enough room in the target array block. There are still items in the source array block to copy. + if (sourceItemIndex + toCopy < blockSize) + { + sourceItemIndex += toCopy; + targetBlockIndex++; + targetItemIndex = 0; + } + // We copied all the source items in the current array block. But have we filled the target? + else + { + sourceItemIndex = 0; + sourceBlockIndex++; + if (targetItemIndex + toCopy < blockSize) + { + targetItemIndex += toCopy; + } + else + { + targetBlockIndex++; + targetItemIndex = 0; + } + } + } + } + + private static void ThrowArgumentOutOfRangeException(in string message) => throw new ArgumentOutOfRangeException("index", message); + + protected void AddRangeEnumerated(IEnumerable source, int growByCount) + { + long i, copied = _longCount; + int blockSize = _blockSize, targetItemIdx = _nextItemIndex, targetBlockIdx = _nextItemBlockIndex; + + using IEnumerator enumerator = source.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return; + } + + long capacity = _capacity; + long available = capacity - copied; + + Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); + Span blockArraySpan; + var memoryBlocksCount = memoryBlocksSpan.Length; + while (true) + { + if (copied + growByCount > capacity) + { + capacity = EnsureCapacity(this, capacity + growByCount); + memoryBlocksCount = _memoryBlocks.Length; + if (blockSize != _blockSize) + { + blockSize = _blockSize; + } + + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + available = capacity - copied; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + for (i = 1; i <= available; i++) + { + blockArraySpan[targetItemIdx++] = enumerator.Current; + + if (!enumerator.MoveNext()) + { + if (targetItemIdx < blockSize) + { + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + } + else + { + _nextItemBlockIndex = targetBlockIdx + 1; + _nextItemIndex = 0; + } + + _longCount += copied + i - _longCount; + return; + } + + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + copied += available; + } + } + + protected void AddRangeWithKnownCount(IEnumerable source, int requiredAdditionalCapacity) + { + long copied = _longCount; + int blockSize = _blockSize; + int targetItemIdx = _nextItemIndex; + int targetBlockIdx = _nextItemBlockIndex; + + Span memoryBlocksSpan; + Span blockArraySpan; + long requiredCapacity = copied + requiredAdditionalCapacity; + if (_capacity < requiredCapacity) + { + _ = EnsureCapacity(this, requiredCapacity); + blockSize = _blockSize; + } + + var memoryBlocksCount = ReservedBlockCount; + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + foreach (var item in source) + { + blockArraySpan[targetItemIdx++] = item; + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + _longCount = requiredCapacity; + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + return; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) + { + Span memoryBlocksSpan = new(memoryBlocks, 0, lastBlockIndex + 1); + int itemIndex, blockIndex; + for (blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); + if (itemIndex >= 0) + { + return itemIndex + ((long)blockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))); + } + } + + if (blockIndex == lastBlockIndex) + { + itemIndex = nextItemIndex != 0 + ? Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, nextItemIndex) + : Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, blockSize); + } + else + { + return ItemNotFoundIndex; + } + + return itemIndex >= 0 ? itemIndex + ((long)lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndex; + } + + /// + /// Creates a set of new memory buffers, if needed, to allow storing at minimum no. of items. + /// + /// 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. + /// 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. + /// + /// This method checks for integral overflow. + /// + /// The maximum no. of items can store. + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static long Resize(RecyclableListV2 list, int minBlockSize, byte minBlockSizeBitShift, 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 blockIndex; + Span memoryBlocksSpan; + + // Release excessive blocks if we're downsizing + if (requiredBlockCount < sourceBlockCount && (minBlockSize >= RecyclableDefaults.MinPooledArrayLength)) + { + memoryBlocksSpan = new(list._memoryBlocks, requiredBlockCount, sourceBlockCount - requiredBlockCount); + blockIndex = 0; + while (blockIndex < memoryBlocksSpan.Length) + { + blockArrayPool.Return(memoryBlocksSpan[blockIndex++]!, NeedsClearing); + } + } + + ArrayPool memoryBlocksPool = list._memoryBlocksPool; + + // Allocate new memory block for all arrays + T[][]? newMemoryBlocks = requiredBlockCount < RecyclableDefaults.MinPooledArrayLength + ? (new T[requiredBlockCount][]) + : memoryBlocksPool.Rent(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, newMemoryBlocks.Length - sourceBlockCount); + if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) + { + if (sourceBlockCount == 0) + { + memoryBlocksSpan[0] = blockArrayPool.Rent(minBlockSize); + minBlockSize = memoryBlocksSpan[0].Length; + blockIndex = 1; + } + else + { + blockIndex = 0; + } + + while (blockIndex < memoryBlocksSpan.Length) + { + memoryBlocksSpan[blockIndex++] = blockArrayPool.Rent(minBlockSize); + } + } + else + { + blockIndex = 0; + while (blockIndex < memoryBlocksSpan.Length) + { + memoryBlocksSpan[blockIndex++] = new T[minBlockSize]; + } + } + } + + return (long)newMemoryBlocks.Length << minBlockSizeBitShift; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + public static long EnsureCapacity(RecyclableListV2 list, long requestedCapacity) + { + long newCapacity = list._capacity > 0 + ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) + : requestedCapacity; + + int blockSize = list._blockSize; + newCapacity = RecyclableListV2.Resize(list, blockSize, list._blockSizePow2Shift, newCapacity); + if (newCapacity > 0 && blockSize != list._memoryBlocks[0].Length) + { + blockSize = list._memoryBlocks[0].Length; + if (blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + list._blockArrayPool = RecyclableArrayPool.Shared(blockSize); + } + + list._blockSizePow2Shift = (byte)MathUtils.GetPow2Shift(blockSize); + list._blockSize = blockSize; + } + + list._capacity = newCapacity; + return newCapacity; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + public RecyclableListV2(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + { + _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)); + _capacity = Resize(this, minBlockSize, _blockSizePow2Shift, expectedItemsCount.Value); + if (_memoryBlocks!.Length > 0 && _blockSize != _memoryBlocks[0].Length) + { + _blockSize = _memoryBlocks[0].Length; + _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)_blockSize)); + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + _blockArrayPool = RecyclableArrayPool.Shared(_blockSize); + } + } + else + { + _blockSize = minBlockSize; + } + } + else + { + _blockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)_blockSize)); + _blockArrayPool = _blockSize >= RecyclableDefaults.MinPooledArrayLength + ? blockArrayPool ?? RecyclableArrayPool.Shared(_blockSize) + : RecyclableArrayPool.Null; + + _memoryBlocks = _emptyMemoryBlocksArray; + } + } + + public RecyclableListV2(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + { + memoryBlocksPool ??= _defaultMemoryBlocksPool; + _memoryBlocksPool = memoryBlocksPool; + + 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)); + _capacity = Resize(this, minBlockSize, _blockSizePow2Shift, expectedItemsCount.Value); + if (_memoryBlocks?.Length > 0 && minBlockSize != _memoryBlocks[0].Length) + { + _blockSize = _memoryBlocks[0].Length; + _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + _blockArrayPool = RecyclableArrayPool.Shared(_blockSize); + } + } + else + { + _blockSize = minBlockSize; + } + } + else + { + _blockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + _blockArrayPool = _blockSize >= RecyclableDefaults.MinPooledArrayLength + ? blockArrayPool ?? RecyclableArrayPool.Shared(_blockSize) + : RecyclableArrayPool.Null; + + _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)_blockSize)); + _memoryBlocks = _emptyMemoryBlocksArray; + } + + AddRange(source); + } + + public T this[long index] + { + get => _memoryBlocks[index >> _blockSizePow2Shift][index & (_blockSize - 1)]; + set => new Span(_memoryBlocks[checked((int)(index >> _blockSizePow2Shift))])[checked((int)(index & (_blockSize - 1)))] = value; + } + + public T this[int index] + { + get => _memoryBlocks[index >> _blockSizePow2Shift][index & (_blockSize - 1)]; + set => new Span(_memoryBlocks[index >> _blockSizePow2Shift])[index & (_blockSize - 1)] = value; + } + + public T[][] MemoryBlocks { get => _memoryBlocks; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(T item) + { + if (_capacity < _longCount + 1) + { + _ = EnsureCapacity(this, _longCount + 1); + } + + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex++] = item; + if (_nextItemIndex == _blockSize) + { + _nextItemBlockIndex++; + _nextItemIndex = 0; + } + + _longCount++; + } + + public void AddRange(in T[] items) + { + if (items.LongLength == 0) + { + return; + } + + long targetCapacity = _longCount + items.LongLength; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _memoryBlocks.Length; + + Span itemsSpan = new(items); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + if (itemsSpan.Length > 0) + { + itemsSpan.CopyTo(targetBlockArraySpan); + } + + _nextItemIndex = itemsSpan.Length; + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableArrayList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _memoryBlocks.Length; + + Span itemsSpan = new(items.AsArray(), 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + _nextItemIndex = itemsSpan.Length; + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableListV2 items) + { + if (items.LongCount == 0) + { + return; + } + + long itemsCount = items.LongCount, + targetCapacity = _longCount + itemsCount; + + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + sourceBlockIndex = 0, + targetBlockIndex = _nextItemBlockIndex, + toCopy; + + Span sourceMemoryBlocksSpan = new(items._memoryBlocks, 0, items._memoryBlocks.Length); + Span itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + Span targetMemoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); + Span targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + long copiedCount = 0; + while (copiedCount < itemsCount) + { + toCopy = checked((int)Math.Min(itemsCount - copiedCount, itemsSpan.Length)); + if (targetBlockArraySpan.Length < toCopy) + { + toCopy = targetBlockArraySpan.Length; + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + itemsSpan = itemsSpan[toCopy..]; + copiedCount += toCopy; + } + else + { + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockArraySpan = targetBlockArraySpan[toCopy..]; + itemsSpan = itemsSpan[toCopy..]; + if (itemsSpan.IsEmpty && sourceBlockIndex + 1 < sourceMemoryBlocksSpan.Length) + { + sourceBlockIndex++; + itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + } + + copiedCount += toCopy; + if (targetBlockArraySpan.IsEmpty && copiedCount < itemsCount) + { + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + } + } + + if ((targetCapacity & (blockSize - 1)) == 0) + { + _nextItemIndex = 0; + _nextItemBlockIndex = targetBlockIndex + 1; + } + else + { + _nextItemIndex = checked((int)(targetCapacity & (blockSize - 1))); + _nextItemBlockIndex = targetBlockIndex; + + } + + _longCount = targetCapacity; + } + + public void AddRange(List items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + itemsCount = items.Count, + copied = Math.Min(blockSize - _nextItemIndex, itemsCount); + + Span memoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); + items.CopyTo(0, memoryBlocksSpan[targetBlockIndex], _nextItemIndex, copied); + if (_nextItemIndex + copied == blockSize) + { + targetBlockIndex++; + _nextItemIndex = 0; + } + else + { + _nextItemIndex += copied; + } + + while (blockSize <= itemsCount - copied) + { + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex++], 0, blockSize); + copied += blockSize; + } + + if ((itemsCount - copied < blockSize) && (itemsCount - copied != 0)) + { + _nextItemIndex = itemsCount - copied; + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex], 0, _nextItemIndex); + } + + _nextItemBlockIndex = targetBlockIndex; + _longCount = targetCapacity; + } + + public void AddRange(IList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _memoryBlocks.Length; + + // 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]; + try + { + items.CopyTo(itemsBuffer, 0); + + Span itemsSpan = new(itemsBuffer, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + _nextItemIndex = itemsSpan.Length; + } + finally + { + if (items.Count >= RecyclableDefaults.MinPooledArrayLength) + { + _defaultBlockArrayPool.Return(itemsBuffer, NeedsClearing); + } + } + } + + public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) + { + if (source is RecyclableListV2 sourceRecyclableList) + { + AddRange(sourceRecyclableList); + return; + } + + if (source is RecyclableArrayList sourceRecyclableArrayList) + { + AddRange(sourceRecyclableArrayList); + return; + } + + if (source is T[] sourceArray) + { + AddRange(sourceArray); + return; + } + + if (source is List sourceList) + { + AddRange(sourceList); + return; + } + + if (source is IList sourceIList) + { + AddRange(sourceIList); + return; + } + + if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) + { + AddRangeWithKnownCount(source, requiredAdditionalCapacity); + return; + } + + AddRangeEnumerated(source, growByCount); + } + + public void Clear() + { + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); + int memoryBlocksCount = memoryBlocksSpan.Length; + ArrayPool blockArrayPool = _blockArrayPool; + for (int toRemoveIdx = 0; toRemoveIdx < memoryBlocksCount; toRemoveIdx++) + { + blockArrayPool.Return(memoryBlocksSpan[toRemoveIdx], NeedsClearing); + } + } + + _capacity = 0; + _nextItemBlockIndex = 0; + _nextItemIndex = 0; + _longCount = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(T item) + { + if (_longCount == 0) + { + return false; + } + + if (_nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0)) + { + return Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) >= 0; + } + + Span memoryBlocksSpan = new(_memoryBlocks); + int lastBlockIndex = _nextItemBlockIndex; + for (int blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + if (Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _blockSize) >= 0) + { + return true; + } + } + + return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, _nextItemIndex) >= 0; + } + + public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, arrayIndex); + + public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndex + : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) + ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) + : checked((int)DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex)); + + public void Insert(int index, T item) => throw new NotSupportedException(); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public long LongIndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndex + : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) + ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) + : DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public bool Remove(T item) + { + var index = LongIndexOf(item); + if (index >= 0) + { + _longCount--; + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) { _nextItemIndex--; } else { - _nextItemIndex = blockSize - 1; + _nextItemIndex = _blockSize - 1; _nextItemBlockIndex--; } if (NeedsClearing) { #pragma warning disable CS8601 // In real use cases we'll never access it - new Span(memoryBlocks[_nextItemBlockIndex], _nextItemIndex, 1)[0] = default; + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; #pragma warning restore CS8601 } + + return true; } - private static void ThrowArgumentOutOfRangeException(in string message) => throw new ArgumentOutOfRangeException("index", message); + return false; + } - protected void AddRangeEnumerated(IEnumerable source, int growByCount) + public void RemoveBlock(int index) + { + if (_blockSize < RecyclableDefaults.MinPooledArrayLength) { - long i, copied = _longCount; - - int blockSize = _blockSize, targetItemIdx = _nextItemIndex, targetBlockIdx = _nextItemBlockIndex; - - using IEnumerator enumerator = source.GetEnumerator(); - if (!enumerator.MoveNext()) - { - return; - } - - long capacity = _capacity; - long available = capacity - copied; - - Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); - Span blockArraySpan; - var memoryBlocksCount = memoryBlocksSpan.Length; - while (true) - { - if (copied + growByCount > capacity) - { - capacity = EnsureCapacity(capacity + growByCount); - memoryBlocksCount = _memoryBlocks.Length; - if (blockSize != _blockSize) - { - blockSize = _blockSize; - } - - memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); - available = capacity - copied; - } - - blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); - for (i = 1; i <= available; i++) - { - blockArraySpan[targetItemIdx++] = enumerator.Current; - - if (!enumerator.MoveNext()) - { - if (targetItemIdx < blockSize) - { - _nextItemBlockIndex = targetBlockIdx; - _nextItemIndex = targetItemIdx; - } - else - { - _nextItemBlockIndex = targetBlockIdx + 1; - _nextItemIndex = 0; - } - - _longCount += copied + i - _longCount; - return; - } - - if (targetItemIdx == blockSize) - { - targetBlockIdx++; - targetItemIdx = 0; - - if (targetBlockIdx == memoryBlocksCount) - { - break; - } - - blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); - } - } - - copied += available; - } + _memoryBlocks[index] = _emptyBlockArray; + } + else + { + _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); } - protected void AddRangeWithKnownCount(IEnumerable source, int requiredAdditionalCapacity) + _capacity -= _blockSize; + if (_nextItemBlockIndex == index) { - long copied = _longCount; - int blockSize = _blockSize; - int targetItemIdx = _nextItemIndex; - int targetBlockIdx = _nextItemBlockIndex; - - Span memoryBlocksSpan; - Span blockArraySpan; - long requiredCapacity = copied + requiredAdditionalCapacity; - if (_capacity < requiredCapacity) - { - _ = EnsureCapacity(requiredCapacity); - blockSize = _blockSize; - } - - var memoryBlocksCount = ReservedBlockCount; - memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); - blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); - foreach (var item in source) - { - blockArraySpan[targetItemIdx++] = item; - if (targetItemIdx == blockSize) - { - targetBlockIdx++; - targetItemIdx = 0; - if (targetBlockIdx == memoryBlocksCount) - { - break; - } - - blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); - } - } - - _longCount = requiredCapacity; - _nextItemBlockIndex = targetBlockIdx; - _nextItemIndex = targetItemIdx; - return; - } - - protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) - { - Span memoryBlocksSpan = new(memoryBlocks, 0, lastBlockIndex + 1); - int itemIndex, blockIndex; - for (blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) - { - itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); - if (itemIndex >= 0) - { - return itemIndex + (blockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))); - } - } - - if (blockIndex == lastBlockIndex) - { - itemIndex = nextItemIndex != 0 - ? Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, nextItemIndex) - : Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, blockSize); - } - else - { - return ItemNotFoundIndex; - } - - return itemIndex >= 0 ? itemIndex + (lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndex; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected static long Resize(ref T[][]? source, int minBlockSize, long oldCapacity, long newCapacity, ArrayPool memoryBlocksPool, ArrayPool blockArrayPool) - { - int minBlockSizePowerToShift = 31 - BitOperations.LeadingZeroCount((uint)minBlockSize); - int sourceBlockCount = (int)(oldCapacity >> minBlockSizePowerToShift); - int requiredBlockCount = (int)(newCapacity >> minBlockSizePowerToShift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0); - int blockIndex; - T[][]? newMemoryBlocks; - Span memoryBlocksSpan; - - // Release excessive blocks if we're downsizing - if (requiredBlockCount < sourceBlockCount && (minBlockSize >= RecyclableDefaults.MinPooledArrayLength)) - { - memoryBlocksSpan = new(source, requiredBlockCount, sourceBlockCount - requiredBlockCount); - blockIndex = 0; - while (blockIndex < memoryBlocksSpan.Length) - { - blockArrayPool.Return(memoryBlocksSpan[blockIndex++]!, NeedsClearing); - } - } - - // Allocate new memory block for all arrays - newMemoryBlocks = requiredBlockCount < RecyclableDefaults.MinPooledArrayLength - ? (new T[requiredBlockCount][]) - : memoryBlocksPool.Rent(requiredBlockCount); - - // Copy arrays from the old memory block for all arrays - if (sourceBlockCount > 0) - { - Array.Copy(source!, newMemoryBlocks, sourceBlockCount); - // We can now return the old memory block for all arrays itself - if (sourceBlockCount >= RecyclableDefaults.MinPooledArrayLength) - { - memoryBlocksPool.Return(source!, true); - } - } - - source = newMemoryBlocks; - - // Allocate arrays for any new blocks - if (requiredBlockCount > sourceBlockCount) - { - memoryBlocksSpan = new(newMemoryBlocks, sourceBlockCount, newMemoryBlocks.Length - sourceBlockCount); - if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) - { - if (sourceBlockCount == 0) - { - memoryBlocksSpan[0] = blockArrayPool.Rent(minBlockSize); - minBlockSize = memoryBlocksSpan[0].Length; - blockIndex = 1; - } - else - { - blockIndex = 0; - } - - while (blockIndex < memoryBlocksSpan.Length) - { - memoryBlocksSpan[blockIndex++] = blockArrayPool.Rent(minBlockSize); - } - } - else - { - blockIndex = 0; - while (blockIndex < memoryBlocksSpan.Length) - { - memoryBlocksSpan[blockIndex++] = new T[minBlockSize]; - } - } - } - - return newMemoryBlocks.Length << minBlockSizePowerToShift; - } - - internal long EnsureCapacity(long requestedCapacity) - { - long newCapacity = _capacity > 0 - ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) - : requestedCapacity; - - newCapacity = Resize(ref _memoryBlocks!, _blockSize, _capacity, newCapacity, _memoryBlocksPool, _blockArrayPool); - if (newCapacity > 0 && _capacity == 0) - { - _blockSize = _memoryBlocks[0].Length; - _blockSizePow2Shift = MathUtils.GetPow2Shift(_blockSize); - } - - _capacity = newCapacity; - return newCapacity; - } - - public OldRecyclableList(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) - { - memoryBlocksPool ??= _defaultMemoryBlocksPool; - blockArrayPool ??= _defaultBlockArrayPool; - _memoryBlocksPool = memoryBlocksPool; - _blockArrayPool = blockArrayPool; - - if (expectedItemsCount > 0) - { - _capacity = Resize(ref _memoryBlocks!, (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize), 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); - if (_memoryBlocks.Length > 0) - { - minBlockSize = _memoryBlocks[0].Length; - } - - _blockSize = minBlockSize; - _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); - } - else - { - _blockSize = (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize); - _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); - _memoryBlocks = _emptyMemoryBlocksArray; - } - } - - public OldRecyclableList(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) - { - memoryBlocksPool ??= _defaultMemoryBlocksPool; - blockArrayPool ??= _defaultBlockArrayPool; - _memoryBlocksPool = memoryBlocksPool; - _blockArrayPool = blockArrayPool; - - if (expectedItemsCount > 0) - { - _capacity = Resize(ref _memoryBlocks!, (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize), 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); - if (_memoryBlocks.Length > 0) - { - minBlockSize = _memoryBlocks[0].Length; - } - - _blockSize = minBlockSize; - _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); - } - else - { - _blockSize = (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize); - _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); - _memoryBlocks = _emptyMemoryBlocksArray; - } - - AddRange(source); - } - - public T this[long index] - { - get => _memoryBlocks[index >> _blockSizePow2Shift][index & (_blockSize - 1)]; - set => new Span(_memoryBlocks[(int)(index >> _blockSizePow2Shift)])[(int)(index & (_blockSize - 1))] = value; - } - - public T this[int index] - { - get => _memoryBlocks[index >> _blockSizePow2Shift][index & (_blockSize - 1)]; - set => new Span(_memoryBlocks[index >> _blockSizePow2Shift])[index & (_blockSize - 1)] = value; - } - - public T[][] MemoryBlocks { get => _memoryBlocks; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Add(T item) - { - if (_capacity < _longCount + 1) - { - _ = EnsureCapacity(_longCount + 1); - } - - _memoryBlocks[_nextItemBlockIndex][_nextItemIndex++] = item; - if (_nextItemIndex == _blockSize) - { - _nextItemBlockIndex++; - _nextItemIndex = 0; - } - - _longCount++; - } - - public void AddRange(in T[] items) - { - if (items.LongLength == 0) - { - return; - } - - long targetCapacity = _longCount + items.LongLength; - if (_capacity < targetCapacity) - { - _ = EnsureCapacity(targetCapacity); - } - - int blockSize = _blockSize, - targetBlockIndex = _nextItemBlockIndex, - memoryBlockCount = _memoryBlocks.Length; - - Span itemsSpan = new(items); - Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); - Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); - while (targetBlockArraySpan.Length <= itemsSpan.Length) - { - itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); - itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; - targetBlockIndex++; - if (targetBlockIndex == memoryBlockCount) - { - break; - } - - targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); - } - - if (itemsSpan.Length > 0) - { - itemsSpan.CopyTo(targetBlockArraySpan); - } - - _nextItemIndex = itemsSpan.Length; - _longCount = targetCapacity; - _nextItemBlockIndex = targetBlockIndex; - } - - public void AddRange(RecyclableArrayList items) - { - if (items.Count == 0) - { - return; - } - - long targetCapacity = _longCount + items.Count; - if (_capacity < targetCapacity) - { - _ = EnsureCapacity(targetCapacity); - } - - int blockSize = _blockSize, - targetBlockIndex = _nextItemBlockIndex, - memoryBlockCount = _memoryBlocks.Length; - - Span itemsSpan = new(items.AsArray(), 0, items.Count); - Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); - Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); - while (targetBlockArraySpan.Length <= itemsSpan.Length) - { - itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); - itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; - targetBlockIndex++; - if (targetBlockIndex == memoryBlockCount) - { - break; - } - - targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); - } - - _nextItemIndex = itemsSpan.Length; - itemsSpan.CopyTo(targetBlockArraySpan); - _longCount = targetCapacity; - _nextItemBlockIndex = targetBlockIndex; - } - - public void AddRange(OldRecyclableList items) - { - if (items.LongCount == 0) - { - return; - } - - long itemsCount = items.LongCount, - targetCapacity = _longCount + itemsCount; - - if (_capacity < targetCapacity) - { - _ = EnsureCapacity(targetCapacity); - } - - int blockSize = _blockSize, - sourceBlockIndex = 0, - targetBlockIndex = _nextItemBlockIndex, - toCopy; - - Span sourceMemoryBlocksSpan = new(items._memoryBlocks, 0, items._memoryBlocks.Length); - Span itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); - Span targetMemoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); - Span targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); - long copiedCount = 0; - while (copiedCount < itemsCount) - { - toCopy = (int)Math.Min(itemsCount - copiedCount, itemsSpan.Length); - if (targetBlockArraySpan.Length < toCopy) - { - toCopy = targetBlockArraySpan.Length; - itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); - targetBlockIndex++; - targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); - itemsSpan = itemsSpan[toCopy..]; - copiedCount += toCopy; - } - else - { - itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); - targetBlockArraySpan = targetBlockArraySpan[toCopy..]; - itemsSpan = itemsSpan[toCopy..]; - if (itemsSpan.IsEmpty && sourceBlockIndex + 1 < sourceMemoryBlocksSpan.Length) - { - sourceBlockIndex++; - itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); - } - - copiedCount += toCopy; - if (targetBlockArraySpan.IsEmpty && copiedCount < itemsCount) - { - targetBlockIndex++; - targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); - } - } - } - - if ((targetCapacity & (blockSize - 1)) == 0) - { - _nextItemIndex = 0; - _nextItemBlockIndex = targetBlockIndex + 1; - } - else - { - _nextItemIndex = (int)(targetCapacity & (blockSize - 1)); - _nextItemBlockIndex = targetBlockIndex; - - } - - _longCount = targetCapacity; - } - - public void AddRange(List items) - { - if (items.Count == 0) - { - return; - } - - long targetCapacity = _longCount + items.Count; - if (_capacity < targetCapacity) - { - _ = EnsureCapacity(targetCapacity); - } - - int blockSize = _blockSize, - targetBlockIndex = _nextItemBlockIndex, - itemsCount = items.Count, - copied = Math.Min(blockSize - _nextItemIndex, itemsCount); - - Span memoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); - items.CopyTo(0, memoryBlocksSpan[targetBlockIndex], _nextItemIndex, copied); - if (_nextItemIndex + copied == blockSize) - { - targetBlockIndex++; - _nextItemIndex = 0; - } - else - { - _nextItemIndex += copied; - } - - while (blockSize <= itemsCount - copied) - { - items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex++], 0, blockSize); - copied += blockSize; - } - - if ((itemsCount - copied < blockSize) && (itemsCount - copied != 0)) - { - _nextItemIndex = itemsCount - copied; - items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex], 0, _nextItemIndex); - } - - _nextItemBlockIndex = targetBlockIndex; - _longCount = targetCapacity; - } - - public void AddRange(IList items) - { - if (items.Count == 0) - { - return; - } - - long targetCapacity = _longCount + items.Count; - if (_capacity < targetCapacity) - { - _ = EnsureCapacity(targetCapacity); - } - - int blockSize = _blockSize, - targetBlockIndex = _nextItemBlockIndex, - memoryBlockCount = _memoryBlocks.Length; - - T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? _blockArrayPool.Rent(items.Count) : new T[items.Count]; - try - { - items.CopyTo(itemsBuffer, 0); - - Span itemsSpan = new(itemsBuffer, 0, items.Count); - Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); - Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); - while (targetBlockArraySpan.Length <= itemsSpan.Length) - { - itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); - itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; - targetBlockIndex++; - if (targetBlockIndex == memoryBlockCount) - { - break; - } - - targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); - } - - itemsSpan.CopyTo(targetBlockArraySpan); - _longCount = targetCapacity; - _nextItemBlockIndex = targetBlockIndex; - _nextItemIndex = itemsSpan.Length; - } - finally - { - if (items.Count >= RecyclableDefaults.MinPooledArrayLength) - { - _blockArrayPool.Return(itemsBuffer, NeedsClearing); - } - } - } - - public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) - { - if (source is OldRecyclableList sourceRecyclableList) - { - AddRange(sourceRecyclableList); - return; - } - - if (source is RecyclableArrayList sourceRecyclableArrayList) - { - AddRange(sourceRecyclableArrayList); - return; - } - - if (source is T[] sourceArray) - { - AddRange(sourceArray); - return; - } - - if (source is List sourceList) - { - AddRange(sourceList); - return; - } - - if (source is IList sourceIList) - { - AddRange(sourceIList); - return; - } - - if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) - { - AddRangeWithKnownCount(source, requiredAdditionalCapacity); - return; - } - - AddRangeEnumerated(source, growByCount); - } - - public void Clear() - { - if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) - { - Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); - int memoryBlocksCount = memoryBlocksSpan.Length; - ArrayPool blockArrayPool = _blockArrayPool; - for (int toRemoveIdx = 0; toRemoveIdx < memoryBlocksCount; toRemoveIdx++) - { - blockArrayPool.Return(memoryBlocksSpan[toRemoveIdx], NeedsClearing); - } - } - - _capacity = 0; - _nextItemBlockIndex = 0; _nextItemIndex = 0; - _longCount = 0; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Contains(T item) + else if (_nextItemBlockIndex > index) { - if (_longCount == 0) - { - return false; - } - - if (_nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0)) - { - return Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) >= 0; - } - - Span memoryBlocksSpan = new(_memoryBlocks); - int lastBlockIndex = _nextItemBlockIndex; - for (int blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) - { - if (Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _blockSize) >= 0) - { - return true; - } - } - - return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, _nextItemIndex) >= 0; - } - - public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, arrayIndex); - - public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int IndexOf(T item) => _longCount == 0 - ? ItemNotFoundIndex - : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) - ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) - : (int)DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex); - - public void Insert(int index, T item) => throw new NotSupportedException(); - public long LongIndexOf(T item) => _longCount == 0 - ? ItemNotFoundIndex - : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) - ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) - : DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex); - - public bool Remove(T item) - { - var itemIndex = LongIndexOf(item); - if (itemIndex >= 0) - { - DoRemoveAt(itemIndex); - return true; - } - - return false; - } - - public void RemoveBlock(int index) - { - if (_blockSize < RecyclableDefaults.MinPooledArrayLength) - { - _memoryBlocks[index] = _emptyBlockArray; - } - else - { - _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); - } - - _capacity -= _blockSize; - if (_nextItemBlockIndex == index) - { - _nextItemIndex = 0; - } - else if (_nextItemBlockIndex > index) - { - _nextItemBlockIndex--; - } - } - - public void RemoveAt(int index) - { - if (index > _longCount - 1 || index < 0) - { - ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); - } - - DoRemoveAt(index); - } - - public void RemoveAt(long index) - { - if (index > _longCount - 1 || index < 0) - { - ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); - } - - DoRemoveAt(index); - } - - IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); - - ~OldRecyclableList() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(); - } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - if (_capacity > 0) - { - Clear(); - if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength) - { - _memoryBlocksPool.Return(_memoryBlocks, NeedsClearing); - } - - GC.SuppressFinalize(this); - } + _nextItemBlockIndex--; } } - - #endregion + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void RemoveAt(int index) + { + if (index >= _longCount || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + _longCount--; + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSize - 1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void RemoveAt(long index) + { + if (index >= _longCount || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + _longCount--; + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSize - 1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + } + + IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + ~RecyclableListV2() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + if (_capacity > 0) + { + Clear(); + if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength) + { + _memoryBlocksPool.Return(_memoryBlocks, NeedsClearing); + } + + GC.SuppressFinalize(this); + } + } + } + + #endregion + public partial class RecyclableListPocBenchmarks + { + + [Benchmark(Baseline = false)] + public void RecyclableList_EnsureCapacityV1_ByPowOf2() + { + using var list = new RecyclableListV1(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + _ = list.EnsureCapacity(TestObjectCount); + } [Benchmark(Baseline = true)] - public void RecyclableList_EnsureCapacity_Old_ByPowOf2() + public void RecyclableList_EnsureCapacityV2_ByPowOf2() { - using var list = new OldRecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); - _ = list.EnsureCapacity(TestObjectCount); + using var list = new RecyclableListV2(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + _ = RecyclableListV2.EnsureCapacity(list, TestObjectCount); } [Benchmark(Baseline = false)] @@ -892,12 +1868,19 @@ namespace Recyclable.Collections.Benchmarks.POC } [Benchmark(Baseline = false)] - public void RecyclableList_EnsureCapacity_Old_ByBlockSize() + public void RecyclableList_EnsureCapacityV1_ByBlockSize() { - using var list = new OldRecyclableList(); + using var list = new RecyclableListV1(); _ = list.EnsureCapacity(TestObjectCount); } + [Benchmark(Baseline = false)] + public void RecyclableList_EnsureCapacityV2_ByBlockSize() + { + using var list = new RecyclableListV2(); + _ = RecyclableListV2.EnsureCapacity(list, TestObjectCount); + } + [Benchmark(Baseline = false)] public void RecyclableList_EnsureCapacity_ByBlockSize() { diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs index 738c0ea..1b48f5a 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs @@ -5,9 +5,11 @@ public static void Run() { var benchmark = new RecyclableListPocBenchmarks(); - benchmark.RecyclableList_EnsureCapacity_Old_ByPowOf2(); + benchmark.RecyclableList_EnsureCapacityV1_ByPowOf2(); + benchmark.RecyclableList_EnsureCapacityV2_ByPowOf2(); benchmark.RecyclableList_EnsureCapacity_ByPowOf2(); - benchmark.RecyclableList_EnsureCapacity_Old_ByBlockSize(); + benchmark.RecyclableList_EnsureCapacityV1_ByBlockSize(); + benchmark.RecyclableList_EnsureCapacityV2_ByBlockSize(); benchmark.RecyclableList_EnsureCapacity_ByBlockSize(); } }