From f94e91c45d8b6b200cbd26332acaeef0f7486916 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 22 Jan 2023 23:16:21 +0100 Subject: [PATCH] RecyclableList.AddRange(IEnumerable) made similar in behavior / optimizations to RecyclableArrayList. Variables named made similar. --- Recyclable.Collections/RecyclableList.cs | 193 +++++++++++++++++++---- 1 file changed, 163 insertions(+), 30 deletions(-) diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 7d098d3..9e3e4dc 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -11,6 +11,7 @@ namespace Recyclable.Collections private static readonly ArrayPool _blockArrayPool = ArrayPool.Create(); private static readonly T[][] _emptyBlockArray = new T[0][]; private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; + private readonly int _blockSize; protected T[][] _memoryBlocks; @@ -57,36 +58,47 @@ namespace Recyclable.Collections } [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected static T[][] SetNewLength(in T[][]? source, in int blockSize, in long newSize) + protected static T[][] SetNewLength(in T[][]? source, in int blockSize, in long newCapacity) { ArrayPool arrayPool = _arrayPool; ArrayPool blockArrayPool = _blockArrayPool; var sourceBlockCount = source?.Length ?? 0; - int requiredBlockCount = (int)(newSize / blockSize) + (newSize % blockSize > 0 ? 1 : 0); + int requiredBlockCount = (int)(newCapacity / blockSize) + (newCapacity % blockSize > 0 ? 1 : 0); if (requiredBlockCount == sourceBlockCount) { return source!; } + //else if (requiredBlockCount < sourceBlockCount) + //{ + // requiredBlockCount = requiredBlockCount > 0 ? requiredBlockCount : 1; + // while (requiredBlockCount < sourceBlockCount) + // { + // requiredBlockCount *= 2; + // } + //} Span newArraySpan; T[][] newMemoryBlocks = requiredBlockCount.RentArrayFromPool(arrayPool); switch (requiredBlockCount >= sourceBlockCount) { case true: - if (sourceBlockCount > 0) + switch (sourceBlockCount > 0) { - Memory sourceMemory = new(source); - Memory newArrayMemory = new(newMemoryBlocks); - sourceMemory.CopyTo(newArrayMemory); - source!.ReturnToPool(arrayPool); - newArraySpan = new Span(newMemoryBlocks)[sourceBlockCount..]; - } - else - { - newArraySpan = new Span(newMemoryBlocks); + case true: + Memory sourceMemory = new(source); + Memory newArrayMemory = new(newMemoryBlocks); + sourceMemory.CopyTo(newArrayMemory); + source!.ReturnToPool(arrayPool); + newArraySpan = new Span(newMemoryBlocks)[sourceBlockCount..]; + break; + + case false: + newArraySpan = new Span(newMemoryBlocks); + break; } - for (int i = 0; i < newArraySpan.Length; i++) + int uninitializedBlocksCount = newArraySpan.Length; + for (int i = 0; i < uninitializedBlocksCount; i++) { newArraySpan[i] = blockSize.RentArrayFromPool(blockArrayPool); } @@ -98,9 +110,10 @@ namespace Recyclable.Collections newArraySpan = new Span(newMemoryBlocks); sourceSpan.CopyTo(newArraySpan); sourceSpan = new Span(source)[newArraySpan.Length..]; - for (int i = 0; i < sourceSpan.Length; i++) + int sourceLength = sourceSpan.Length; + for (int i = 0; i < sourceLength; i++) { - sourceSpan[i].ReturnToPool(_blockArrayPool); + sourceSpan[i].ReturnToPool(blockArrayPool); } source!.ReturnToPool(arrayPool); @@ -174,7 +187,7 @@ namespace Recyclable.Collections public T this[long index] { get => _memoryBlocks[(int)(index / _blockSize)][index % _blockSize]; - set => new Span(_memoryBlocks[(int)(index / _blockSize)])[(int)index % _blockSize] = value; + set => new Span(_memoryBlocks[(int)(index / _blockSize)])[(int)(index % _blockSize)] = value; } public T this[int index] @@ -193,7 +206,7 @@ namespace Recyclable.Collections _ = EnsureCapacity(requiredCapacity); } - _memoryBlocks[(int)(oldCount / blockSize)][oldCount % blockSize] = item; + _memoryBlocks[(int)(oldCount / blockSize)][(int)(oldCount % blockSize)] = item; _longCount++; } @@ -210,15 +223,21 @@ namespace Recyclable.Collections int blockSize = _blockSize; int targetItemIdx = (int)(oldLongCount % blockSize); int targetBlockIdx = (int)(oldLongCount / blockSize) + (targetItemIdx > 0 ? 1 : 0); - var memoryBlockSpan = new Span(_memoryBlocks); - var targetBlockSpan = new Span(memoryBlockSpan[targetBlockIdx]); - for (long i = 0; i < sourceItemsCount; i++) + var memoryBlocksSpan = new Span(_memoryBlocks); + var memoryBlocksCount = memoryBlocksSpan.Length; + var targetBlockSpan = new Span(memoryBlocksSpan[targetBlockIdx]); + for (var i = 0L; i < sourceItemsCount; i++) { targetBlockSpan[targetItemIdx++] = items[i]; if (targetItemIdx == blockSize) { targetBlockIdx++; - targetBlockSpan = new Span(memoryBlockSpan[targetBlockIdx]); + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + targetBlockSpan = new Span(memoryBlocksSpan[targetBlockIdx]); targetItemIdx = 0; } } @@ -239,15 +258,21 @@ namespace Recyclable.Collections int blockSize = _blockSize; int targetItemIdx = (int)(oldLongCount % blockSize); int targetBlockIdx = (int)(oldLongCount / blockSize) + (targetItemIdx > 0 ? 1 : 0); - var memoryBlockSpan = new Span(_memoryBlocks); - var targetBlockSpan = new Span(memoryBlockSpan[targetBlockIdx]); + var memoryBlocksSpan = new Span(_memoryBlocks); + var memoryBlocksCount = memoryBlocksSpan.Length; + var targetBlockSpan = new Span(memoryBlocksSpan[targetBlockIdx]); for (int i = 0; i < sourceItemsCount; i++) { targetBlockSpan[targetItemIdx++] = items[i]; if (targetItemIdx == blockSize) { targetBlockIdx++; - targetBlockSpan = new Span(memoryBlockSpan[targetBlockIdx]); + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + targetBlockSpan = new Span(memoryBlocksSpan[targetBlockIdx]); targetItemIdx = 0; } } @@ -268,15 +293,21 @@ namespace Recyclable.Collections int blockSize = _blockSize; int targetItemIdx = (int)(oldLongCount % blockSize); int targetBlockIdx = (int)(oldLongCount / blockSize) + (targetItemIdx > 0 ? 1 : 0); - var memoryBlockSpan = new Span(_memoryBlocks); - var targetBlockSpan = new Span(memoryBlockSpan[targetBlockIdx]); + var memoryBlocksSpan = new Span(_memoryBlocks); + var memoryBlocksCount = memoryBlocksSpan.Length; + var targetBlockSpan = new Span(memoryBlocksSpan[targetBlockIdx]); for (int i = 0; i < sourceItemsCount; i++) { targetBlockSpan[targetItemIdx++] = items[i]; if (targetItemIdx == blockSize) { targetBlockIdx++; - targetBlockSpan = new Span(memoryBlockSpan[targetBlockIdx]); + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + targetBlockSpan = new Span(memoryBlocksSpan[targetBlockIdx]); targetItemIdx = 0; } } @@ -284,12 +315,114 @@ namespace Recyclable.Collections _longCount = targetCapacity; } - private static void AddRange(RecyclableList destination, IEnumerable source) + private void AddRange(RecyclableList destination, IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) { - foreach (var item in source) + if (source is T[] sourceArray) { - destination.Add(item); + AddRange(sourceArray); + return; } + + if (source is List sourceList) + { + AddRange(sourceList); + return; + } + + if (source is RecyclableArrayList sourceRecyclableArrayList) + { + AddRange(sourceRecyclableArrayList); + return; + } + + if (source is IList sourceIList) + { + AddRange(sourceIList); + return; + } + + long capacity = _capacity; + long oldLongCount = _longCount; + int blockSize = _blockSize; + int targetItemIdx = (int)(oldLongCount % blockSize); + int targetBlockIdx = (int)(targetItemIdx / blockSize) + (targetItemIdx > 0 ? 1 : 0); + + Span memoryBlocksSpan; + Span blockArraySpan; + if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) + { + _ = EnsureCapacity(targetItemIdx + requiredAdditionalCapacity); + memoryBlocksSpan = new(_memoryBlocks); + var memoryBlocksCount = memoryBlocksSpan.Length; + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx]); + foreach (var item in source) + { + blockArraySpan[targetItemIdx++] = item; + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + targetItemIdx = 0; + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx]); + } + } + + _longCount = targetItemIdx; + return; + } + + long i; + using var enumerator = source.GetEnumerator(); + + if (enumerator.MoveNext()) + { + long available = capacity - targetItemIdx; + memoryBlocksSpan = new(_memoryBlocks); + var memoryBlocksCount = memoryBlocksSpan.Length; + while (true) + { + if (targetItemIdx + growByCount > capacity) + { + capacity = EnsureCapacity(capacity + growByCount); + memoryBlocksSpan = new(_memoryBlocks); + memoryBlocksCount = memoryBlocksSpan.Length; + available = capacity - targetItemIdx; + } + + blockArraySpan = memoryBlocksSpan[targetBlockIdx]; + for (i = 0; i < available; i++) + { + blockArraySpan[targetItemIdx++] = enumerator.Current; + if (!enumerator.MoveNext()) + { + break; + } + + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + targetItemIdx = 0; + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx]); + } + } + + if (i < available) + { + break; + } + } + } + + _longCount = targetItemIdx; } public void Clear()