Solutions from RecyclableArreyList were copied to RecylableList. Initial version.

This commit is contained in:
mlemanczyk committed 2023-01-21 18:50:21 +01:00
1 parent 0c3847a9bf
commit 6ef79d2203
11 files changed
+453 -229

No files matched your search

+29 -4
View File
@@ -1,14 +1,39 @@
# Recyclable.Collections
`Recyclable.Collections` project is an open source framework for operating dynamic lists at performance close to raw arrays, but fairly unlimited in size. It aims at providing minimal memory footprint. It implements `IList<T>`'s interface and is targeted as direct replacements of `List<T>`, `SortableList<T>`, `PriorityQueue<T>` & similar.
## Included
* `RecyclableArrayList<T>`
* `RecyclableList<T>`
* `RecyclableSortableList<T>`
* Range: `long`
* Interfaces: `IList<T>`, `ILongList<T>`, `IEnumerable<T>`, `IDisposable`
* `RecyclableQueue<T>`
* Range: `long`
* Interfaces: `IList<T>`, `ILongList<T>`, `IEnumerable<T>`, `IDisposable`
* `RecyclableStack<T>`
* Range: `long`
* Interfaces: `IList<T>`, `ILongList<T>`, `IEnumerable<T>`, `IDisposable`
# Characteristicts of the classes
* All classes provide large storage capabilities, in practice limited only by the memory available in your system.
* All data is stored in blocks, with the provided `int blockSize` or the default, currently being `10,240` items in each block. It's recommended to use smaller numbers, if you foresee storing low no. of items on the list.
## Common
* All classes implement `IDisposable` interface & SHOULD be disposed after use. That's to return block arrays taken from the shared pool. It may be foreseen as an issue for replacement in existing code, which obviously is missing `using` clause. But considering that `Dispose` will be called by `GC` anyway, it should cause issues in specific scenarios, only. In either case the fix is one-word addition of `using`.
* Memory blocks are created in 2 ways, depending on `blockSize` value
* when `blockSize < 100` ⇨ blocks are created as regular `arrays`
* when `blockSize >= 100` ⇨ blocks are taken from and returned to `ArrayPool<T>.Shared`, when blocks are removed
* All classes implement `IDisposable` interface & SHOULD be disposed after use. That's to return block arrays taken from the shared pool. It may be foreseen as an issue for replacement in existing code, which obviously is missing `using` clause. But considering that `Dispose` will be called by `GC` anyway, it should cause issues in specific scenarios, only. In either case the fix is one-word addition of `using`.
* Array pools are shared between the same `T` type. I.e. `List<int>` will use a different pool from `List<short>` and so on. For high concurrency environments you may want to provide your own pools, when this feature becomes available in the upcoming releases.
* Trying to access `this[int index]`, `this[long index]`, `Count`, `LongCount` etc. when `Capacity == 0` will raise `NullReferenceException`. This is by design to remove all non-critical code from the constructor.
## `RecyclableArrayList<T>`
* Range: `int`
* Interfaces: `IList<T>`, `IEnumerable<T>`, `IDisposable`
This is the direct equivalent of `List<T>` class, except that the arrays are taken from the shared pool, instead of directly being allocated. It proves to out perform `List<T>` & the standard arrays in certains operations and is fairly close in others. But they may perform worse in certain scenarios. If you're interested in details, please refer to benchmarks.
## `RecyclableList<T>`, `RecyclableSortableList<T>`, `RecyclableQueue<T>`, `RecyclableStack<T>`
* Range: `long`
* Interfaces: `IList<T>`, `ILongList<T>`, `IEnumerable<T>`, `IDisposable`
* All classes provide large storage capabilities, in practice limited only by the memory available in your system.
* All data is stored in blocks, with the provided `int blockSize` or the default, currently being `10,240` items in each block. It's recommended to use smaller numbers, if you foresee storing low no. of items on the list.
* There is no memory copying to increase the capacity of the recyclable classes, anymore. If more capacity is needed, a new memory block is allocate & added to the internal list of blocks. This is the only list that may re-allocate memory block & copy their content to grow. That shouldn't be an issue considering limited no. of blocks stored by each class in practical scenarios. Currently I'm not planning any works around that. Shall there be a need to eliminate it, you're welcomed to file a PR with the proposed changes.
* `Insert`, `Remove` & `RemoveAt` methods raise `NotSupportedException`. This is by design in the current version, because it involves performance degradadion. I'm planning works around this in the upcoming weeks. If you need that sooner, you're welcomed to file a PR with the proposed changes.
@@ -16,7 +41,7 @@
The architecture of the classes included in this project is driven primarily by performance & memory footprints. The following decisions were made to support it.
## Code duplication
There is some level of code duplication in each of the classes. When you look at them, you will see they are alike & some of the code could be extracted to extension methods. It has been done & proved to perform worse. For this reason, properties & methods expected to be on the hotpath duplicate some code.
There is high level of code duplication in each of the classes. When you look at them, you will see they are alike & some of the code could be extracted to extension methods. It has been done & has proved to perform worse. For this reason, properties & methods expected much of the code is duplicated to get the most out of it.
## Common base `ILongList<T>`
Having a common base class for all the classes looks very attractive. But it is also proved to perform worse, if you start making properties & methods virtual & there are subsequent calls to `base`. For this reason, each of the classes is the root class & there are hardly any virtual methods & properties. All classes are meant to be `final`, but I've not marked them as `sealed`. If you feel that you'd benefit from it, you're welcomed to inherit from them.
@@ -4,8 +4,8 @@ namespace Recyclable.Collections.Benchmarks
{
public class BenchmarkBase
{
[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)]
public int TestObjectCount = 1; // = 1_000_000;
//[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)]
public int TestObjectCount = 1_000_000;
public int BlockSize => TestObjectCount switch
{
0 => 1,
+4 -2
View File
@@ -3,12 +3,14 @@ using Recyclable.Collections.Benchmarks;
//BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(new[] { "-f*" });
BenchmarkRunner.Run<RecyclableCollectionsBenchmarks>();
//BenchmarkRunner.Run<RecyclableCollectionsBenchmarks>();
//BenchmarkRunner.Run<RefVsInstanceMemberBenchmarks>();
//var benchmark = new RecyclableCollectionsBenchmarks();
var benchmark = new RecyclableCollectionsBenchmarks();
benchmark.Setup();
//benchmark.RecyclableArrayList_AddRange_WithCapacity();
//benchmark.RecyclableArrayList_Add_WithCapacity();
benchmark.RecyclableList_Add();
//BenchmarkRunner.Run<DelegateVsComparerBenchmark>();
@@ -4,14 +4,14 @@ namespace Recyclable.Collections.Benchmarks
{
public partial class RecyclableCollectionsBenchmarks : BenchmarkBase
{
//[Benchmark(Baseline = true)]
[Benchmark(Baseline = true)]
public void List_Create()
{
var list = new List<object>();
DoNothing(list);
}
//[Benchmark]
[Benchmark]
public void List_Create_WithCapacity()
{
var list = new List<object>(TestObjectCount);
@@ -30,7 +30,7 @@ namespace Recyclable.Collections.Benchmarks
}
}
[Benchmark(Baseline = true)]
[Benchmark(Baseline = false)]
public void List_Add_WithCapacity()
{
var data = TestObjects;
@@ -31,14 +31,14 @@ namespace Recyclable.Collections.Benchmarks
_testObjects = default;
}
//[Benchmark]
[Benchmark]
public void RecyclableArrayList_Create()
{
using var list = new RecyclableArrayList<object>();
DoNothing(list);
}
//[Benchmark]
[Benchmark]
public void RecyclableArrayList_Create_WithCapacity()
{
using var list = new RecyclableArrayList<object>(TestObjectCount);
@@ -6,28 +6,21 @@ namespace Recyclable.Collections.Benchmarks
{
private RecyclableList<object> _testRecyclableList;
//[Benchmark]
[Benchmark]
public void RecyclableList_Create()
{
using var list = new RecyclableList<object>();
DoNothing(list);
}
//[Benchmark]
public void RecyclableList_Create_0Capacity()
{
using var list = new RecyclableList<object>(totalItemsCount: 0);
DoNothing(list);
}
//[Benchmark]
[Benchmark]
public void RecyclableList_Create_WithCapacity()
{
using var list = new RecyclableList<object>(totalItemsCount: TestObjectCount);
using var list = new RecyclableList<object>(initialCapacity: TestObjectCount);
DoNothing(list);
}
//[Benchmark]
[Benchmark]
public void RecyclableList_Add()
{
var data = TestObjects;
@@ -39,7 +32,7 @@ namespace Recyclable.Collections.Benchmarks
}
}
//[Benchmark]
[Benchmark]
public void RecyclableList_Add_WithCapacity()
{
var data = TestObjects;
+37 -36
View File
@@ -9,7 +9,7 @@ namespace Recyclable.Collections
private static readonly ArrayPool<T> _arrayPool = ArrayPool<T>.Create();
private static readonly IEqualityComparer<T> _equalityComparer = EqualityComparer<T>.Default;
protected T[] _memory;
protected T[] _memoryBlock;
private static void ThrowArgumentOutOfRangeException()
{
@@ -21,33 +21,33 @@ namespace Recyclable.Collections
{
ArrayPool<T> arrayPool = _arrayPool;
var sourceLength = source?.Length ?? 0;
T[] newArray = newSize.RentArrayFromPool(arrayPool);
T[] newMemoryBlock = newSize.RentArrayFromPool(arrayPool);
switch (newSize >= sourceLength)
{
case true:
if (sourceLength > 0)
{
Memory<T> sourceMemory = new(source);
Memory<T> newArrayMemory = new(newArray);
Memory<T> newArrayMemory = new(newMemoryBlock);
sourceMemory.CopyTo(newArrayMemory);
source!.ReturnToPool(arrayPool);
}
return newArray;
return newMemoryBlock;
case false:
var sourceSpan = new Span<T>(source)[..newSize];
Span<T> newArraySpan = new(newArray);
Span<T> newArraySpan = new(newMemoryBlock);
sourceSpan.CopyTo(newArraySpan);
source!.ReturnToPool(arrayPool);
return newArray;
return newMemoryBlock;
}
}
protected int EnsureCapacity(in int requestedCapacity)
{
int oldCapacity = _capacity;
ref T[] memory = ref _memory;
ref T[] memory = ref _memoryBlock;
int newCapacity;
switch (oldCapacity > 0)
@@ -84,8 +84,8 @@ namespace Recyclable.Collections
{
if (initialCapacity > 0)
{
_memory = SetNewLength(_memory, initialCapacity);
_capacity = _memory.Length;
_memoryBlock = SetNewLength(_memoryBlock, initialCapacity);
_capacity = _memoryBlock.Length;
}
}
@@ -119,15 +119,15 @@ namespace Recyclable.Collections
public RecyclableArrayList(IEnumerable<T> source, int initialCapacity = RecyclableDefaults.Capacity)
{
_memory = SetNewLength(_memory, initialCapacity);
_capacity = _memory.Length;
_memoryBlock = SetNewLength(_memoryBlock, initialCapacity);
_capacity = _memoryBlock.Length;
AddRange(source);
}
public T this[int index]
{
get => _memory[index];
set => new Span<T>(_memory)[index] = value;
get => _memoryBlock[index];
set => new Span<T>(_memoryBlock)[index] = value;
}
protected int _capacity;
@@ -138,8 +138,8 @@ namespace Recyclable.Collections
{
if (_capacity != value)
{
_memory = SetNewLength(_memory, value);
_capacity = _memory.Length;
_memoryBlock = SetNewLength(_memoryBlock, value);
_capacity = _memoryBlock.Length;
}
}
}
@@ -156,13 +156,13 @@ namespace Recyclable.Collections
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(T item)
{
int requestedCapacity = _count + 1;
if (_capacity < requestedCapacity)
int requiredCapacity = _count + 1;
if (_capacity < requiredCapacity)
{
_ = EnsureCapacity(requestedCapacity);
_ = EnsureCapacity(requiredCapacity);
}
_memory[_count++] = item;
_memoryBlock[_count++] = item;
}
public void AddRange(in T[] items)
@@ -174,7 +174,7 @@ namespace Recyclable.Collections
_ = EnsureCapacity(targetCapacity);
}
var targetSpan = new Span<T>(_memory)[_count..];
var targetSpan = new Span<T>(_memoryBlock)[_count..];
Span<T> itemsSpan = new(items);
itemsSpan.CopyTo(targetSpan);
_count = targetCapacity;
@@ -189,7 +189,7 @@ namespace Recyclable.Collections
_ = EnsureCapacity(targetCapacity);
}
items.CopyTo(_memory, _count);
items.CopyTo(_memoryBlock, _count);
_count = targetCapacity;
}
@@ -202,7 +202,7 @@ namespace Recyclable.Collections
_ = EnsureCapacity(targetCapacity);
}
items.CopyTo(_memory, _count);
items.CopyTo(_memoryBlock, _count);
_count = targetCapacity;
}
@@ -215,8 +215,8 @@ namespace Recyclable.Collections
_ = EnsureCapacity(targetCapacity);
}
var targetSpan = new Span<T>(_memory)[_count..];
Span<T> itemsSpan = new(items._memory);
var targetSpan = new Span<T>(_memoryBlock)[_count..];
Span<T> itemsSpan = new(items._memoryBlock);
itemsSpan.CopyTo(targetSpan);
_count = targetCapacity;
}
@@ -253,7 +253,7 @@ namespace Recyclable.Collections
if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity))
{
_ = EnsureCapacity(targetIndex + requiredAdditionalCapacity);
memorySpan = new(_memory);
memorySpan = new(_memoryBlock);
foreach (var item in source)
{
memorySpan[targetIndex++] = item;
@@ -266,7 +266,7 @@ namespace Recyclable.Collections
int i;
using var enumerator = source.GetEnumerator();
memorySpan = new(_memory);
memorySpan = new(_memoryBlock);
if (enumerator.MoveNext())
{
int available = capacity - targetIndex;
@@ -275,7 +275,7 @@ namespace Recyclable.Collections
if (targetIndex + growByCount > capacity)
{
capacity = EnsureCapacity(capacity + growByCount);
memorySpan = new(_memory);
memorySpan = new(_memoryBlock);
available = capacity - targetIndex;
}
@@ -303,14 +303,14 @@ namespace Recyclable.Collections
_count = 0;
}
public bool Contains(T item) => _memory.Contains(item);
public bool Contains(T item) => _memoryBlock.Contains(item);
public void CopyTo(T[] array, int arrayIndex) => _memory.CopyTo(array, arrayIndex);
public void CopyTo(T[] array, int arrayIndex) => _memoryBlock.CopyTo(array, arrayIndex);
protected static IEnumerable<T> Enumerate(RecyclableArrayList<T> list)
{
int count = list._count;
var memory = list._memory;
var memory = list._memoryBlock;
for (var i = 0; i < count; i++)
{
yield return memory[i];
@@ -319,7 +319,7 @@ namespace Recyclable.Collections
public int IndexOf(T itemToFind)
{
Span<T> memorySpan = new(_memory);
Span<T> memorySpan = new(_memoryBlock);
int itemCount = _count;
var equalityComparer = _equalityComparer;
for (var itemIdx = 0; itemIdx < itemCount; itemIdx++)
@@ -345,12 +345,12 @@ namespace Recyclable.Collections
if (oldCount > 0)
{
var sourceSpan = new Span<T>(_memory)[index..oldCount];
var targetSpan = new Span<T>(_memory)[(index + 1)..];
var sourceSpan = new Span<T>(_memoryBlock)[index..oldCount];
var targetSpan = new Span<T>(_memoryBlock)[(index + 1)..];
sourceSpan.CopyTo(targetSpan);
}
_memory[index] = item;
_memoryBlock[index] = item;
_count++;
}
@@ -365,6 +365,7 @@ namespace Recyclable.Collections
return false;
}
public void RemoveAt(int index)
{
int oldCount = _count;
@@ -381,7 +382,7 @@ namespace Recyclable.Collections
return;
}
_memory.CopyItems(index, oldCountMinus1, ref _memory);
_memoryBlock.CopyItems(index, oldCountMinus1, ref _memoryBlock);
_count--;
}
@@ -394,7 +395,7 @@ namespace Recyclable.Collections
{
Clear();
_capacity = 0;
_memory.ReturnToPool(_arrayPool);
_memoryBlock.ReturnToPool(_arrayPool);
GC.SuppressFinalize(this);
}
}
+321 -130
View File
@@ -1,20 +1,279 @@
using System.Buffers;
using System;
using System.Buffers;
using System.Collections;
using System.Runtime.CompilerServices;
namespace Recyclable.Collections
{
public class RecyclableList<T> : IDisposable, IList<T>
{
private static readonly ArrayPool<T[]> _arrayPool = ArrayPool<T[]>.Create();
private static readonly ArrayPool<T> _blockArrayPool = ArrayPool<T>.Create();
private static readonly T[][] _emptyBlockArray = new T[0][];
private static readonly IEqualityComparer<T> _equalityComparer = EqualityComparer<T>.Default;
private const int _minPooledArraySize = 100;
private readonly int _blockSize;
private readonly RecyclableArrayList<T[]> _memoryBlocks;
protected T[][] _memoryBlocks;
private static void AddCapacity(RecyclableList<T> list, RecyclableArrayList<T[]> memoryBlocks, int additionalCapacity)
private long _capacity;
public long Capacity
{
T[] newMemoryBlock = RentArrayFromPool(additionalCapacity);
memoryBlocks.Add(newMemoryBlock);
list.Capacity += additionalCapacity;
get => _capacity;
protected set => _capacity = value;
}
public int Count => (int)LongCount;
protected long _longCount;
public long LongCount
{
get => _longCount;
set => _longCount = value;
}
public bool IsReadOnly { get; } = false;
public int BlockCount => _memoryBlocks.Length;
private static void RemoveAt(RecyclableList<T> list, long index)
{
long oldCount = list._longCount;
long oldCountMinus1 = oldCount - 1;
if (index != oldCountMinus1)
{
ThrowArgumentOutOfRangeException();
}
list._longCount--;
if (list._capacity - oldCountMinus1 == list._blockSize)
{
list.RemoveBlock(list._memoryBlocks.Length - 1);
}
}
private static void ThrowArgumentOutOfRangeException()
{
throw new ArgumentOutOfRangeException("index");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected static T[][] SetNewLength(in T[][]? source, in int blockSize, in long newSize)
{
ArrayPool<T[]> arrayPool = _arrayPool;
ArrayPool<T> blockArrayPool = _blockArrayPool;
var sourceBlockCount = source?.Length ?? 0;
int requiredBlockCount = (int)(newSize / blockSize) + (newSize % blockSize > 0 ? 1 : 0);
Span<T[]> newArraySpan;
T[][] newMemoryBlocks = requiredBlockCount.RentArrayFromPool(arrayPool);
switch (requiredBlockCount >= sourceBlockCount)
{
case true:
if (sourceBlockCount > 0)
{
Memory<T[]> sourceMemory = new(source);
Memory<T[]> newArrayMemory = new(newMemoryBlocks);
sourceMemory.CopyTo(newArrayMemory);
source!.ReturnToPool(arrayPool);
newArraySpan = new Span<T[]>(newMemoryBlocks)[sourceBlockCount..];
}
else
{
newArraySpan = new Span<T[]>(newMemoryBlocks);
}
for (int i = 0; i < newArraySpan.Length; i++)
{
newArraySpan[i] = blockSize.RentArrayFromPool(blockArrayPool);
}
return newMemoryBlocks;
case false:
var sourceSpan = new Span<T[]>(source)[..requiredBlockCount];
newArraySpan = new Span<T[]>(newMemoryBlocks);
sourceSpan.CopyTo(newArraySpan);
sourceSpan = new Span<T[]>(source)[newArraySpan.Length..];
for (int i = 0; i < sourceSpan.Length; i++)
{
sourceSpan[i].ReturnToPool(_blockArrayPool);
}
source!.ReturnToPool(arrayPool);
return newMemoryBlocks;
}
}
protected long EnsureCapacity(in long requestedCapacity)
{
long oldCapacity = _capacity;
ref T[][] memory = ref _memoryBlocks;
long newCapacity;
switch (oldCapacity > 0)
{
case true:
newCapacity = oldCapacity;
while (newCapacity < requestedCapacity)
{
newCapacity *= 2;
}
break;
case false:
newCapacity = requestedCapacity;
break;
}
memory = SetNewLength(memory, _blockSize, newCapacity);
newCapacity = memory.Length;
_capacity = newCapacity;
return newCapacity;
}
#pragma warning disable CS8618 // _memoryBlocks will be initialized when the 1st item is added
public RecyclableList(int blockSize = RecyclableDefaults.BlockSize, long? initialCapacity = default)
#pragma warning restore CS8618
{
_blockSize = blockSize;
if (initialCapacity > 0)
{
_memoryBlocks = SetNewLength(_memoryBlocks, blockSize, initialCapacity.Value);
_capacity = _memoryBlocks.Length * blockSize;
}
else
{
_memoryBlocks = _emptyBlockArray;
}
}
#pragma warning disable CS8618 // _memoryBlocks will be initialized when the 1st item is added
public RecyclableList(IEnumerable<T> source, int blockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default)
#pragma warning restore CS8618
{
_blockSize = blockSize;
if (expectedItemsCount > 0)
{
_memoryBlocks = SetNewLength(_memoryBlocks, blockSize, expectedItemsCount.Value);
_capacity = _memoryBlocks.Length * blockSize;
}
else
{
_memoryBlocks = _emptyBlockArray;
}
AddRange(this, source);
}
public T this[long index]
{
get => _memoryBlocks[(int)(index / _blockSize)][index % _blockSize];
set => new Span<T>(_memoryBlocks[(int)(index / _blockSize)])[(int)index % _blockSize] = value;
}
public T this[int index]
{
get => _memoryBlocks[index / _blockSize][index % _blockSize];
set => new Span<T>(_memoryBlocks[index / _blockSize])[index % _blockSize] = value;
}
public void Add(T item)
{
int blockSize = _blockSize;
long oldCount = _longCount;
long requiredCapacity = oldCount + 1;
if (_capacity < requiredCapacity)
{
_ = EnsureCapacity(requiredCapacity);
}
_memoryBlocks[(int)(oldCount / blockSize)][oldCount % blockSize] = item;
_longCount++;
}
public void AddRange(in T[] items)
{
long sourceItemsCount = items.LongLength;
long oldLongCount = _longCount;
long targetCapacity = oldLongCount + sourceItemsCount;
if (_capacity < targetCapacity)
{
_ = EnsureCapacity(targetCapacity);
}
int blockSize = _blockSize;
int targetItemIdx = (int)(oldLongCount % blockSize);
int targetBlockIdx = (int)(oldLongCount / blockSize) + (targetItemIdx > 0 ? 1 : 0);
var memoryBlockSpan = new Span<T[]>(_memoryBlocks);
var targetBlockSpan = new Span<T>(memoryBlockSpan[targetBlockIdx]);
for (long i = 0; i < sourceItemsCount; i++)
{
targetBlockSpan[targetItemIdx++] = items[i];
if (targetItemIdx == blockSize)
{
targetBlockIdx++;
targetBlockSpan = new Span<T>(memoryBlockSpan[targetBlockIdx]);
targetItemIdx = 0;
}
}
_longCount = targetCapacity;
}
public void AddRange(in List<T> items)
{
long sourceItemsCount = items.Count;
long oldLongCount = _longCount;
long targetCapacity = oldLongCount + sourceItemsCount;
if (_capacity < targetCapacity)
{
_ = EnsureCapacity(targetCapacity);
}
int blockSize = _blockSize;
int targetItemIdx = (int)(oldLongCount % blockSize);
int targetBlockIdx = (int)(oldLongCount / blockSize) + (targetItemIdx > 0 ? 1 : 0);
var memoryBlockSpan = new Span<T[]>(_memoryBlocks);
var targetBlockSpan = new Span<T>(memoryBlockSpan[targetBlockIdx]);
for (int i = 0; i < sourceItemsCount; i++)
{
targetBlockSpan[targetItemIdx++] = items[i];
if (targetItemIdx == blockSize)
{
targetBlockIdx++;
targetBlockSpan = new Span<T>(memoryBlockSpan[targetBlockIdx]);
targetItemIdx = 0;
}
}
_longCount = targetCapacity;
}
public void AddRange(in IList<T> items)
{
long sourceItemsCount = items.Count;
long oldLongCount = _longCount;
long targetCapacity = oldLongCount + sourceItemsCount;
if (_capacity < targetCapacity)
{
_ = EnsureCapacity(targetCapacity);
}
int blockSize = _blockSize;
int targetItemIdx = (int)(oldLongCount % blockSize);
int targetBlockIdx = (int)(oldLongCount / blockSize) + (targetItemIdx > 0 ? 1 : 0);
var memoryBlockSpan = new Span<T[]>(_memoryBlocks);
var targetBlockSpan = new Span<T>(memoryBlockSpan[targetBlockIdx]);
for (int i = 0; i < sourceItemsCount; i++)
{
targetBlockSpan[targetItemIdx++] = items[i];
if (targetItemIdx == blockSize)
{
targetBlockIdx++;
targetBlockSpan = new Span<T>(memoryBlockSpan[targetBlockIdx]);
targetItemIdx = 0;
}
}
_longCount = targetCapacity;
}
private static void AddRange(RecyclableList<T> destination, IEnumerable<T> source)
@@ -25,125 +284,16 @@ namespace Recyclable.Collections
}
}
private static void RemoveAt(RecyclableList<T> list, long index)
{
if (index == list.LongCount - 1)
{
list.LongCount--;
if (list.Capacity - list.LongCount == list._blockSize)
{
list.RemoveBlock(list.BlockCount - 1);
}
}
else
{
throw new NotSupportedException("Only removal of the last element is supported");
}
}
private static T[] RentArrayFromPool(int minSize) => (minSize >= _minPooledArraySize)
? ArrayPool<T>.Shared.Rent(minSize)
: new T[minSize];
private static void ReturnToPool(in T[] array)
{
if (array.LongLength is < _minPooledArraySize or > int.MaxValue)
{
return;
}
ArrayPool<T>.Shared.Return(array);
}
private static void RemoveBlock(RecyclableList<T> owner, RecyclableArrayList<T[]> memoryBlocks, int blockSize, int index)
{
try
{
ReturnToPool(memoryBlocks[index]);
memoryBlocks.RemoveAt(index);
}
finally
{
owner.Capacity -= blockSize;
}
}
private static RecyclableArrayList<T[]> SetupMemoryBlocks(RecyclableList<T> owner, int blockSize, long? totalItemsCount)
{
if (blockSize <= 0)
{
return new();
}
totalItemsCount ??= 8 * blockSize;
int additionalArray = totalItemsCount % blockSize > 0 ? 1 : 0;
int memoryBlockCount = (int)(totalItemsCount.Value / blockSize).LimitTo(int.MaxValue) + additionalArray;
RecyclableArrayList<T[]> memoryBlocks = new(memoryBlockCount);
for (var blockIdx = 0; blockIdx < memoryBlockCount; blockIdx++)
{
memoryBlocks[blockIdx] = RentArrayFromPool(blockSize);
}
memoryBlocks.Count = memoryBlockCount;
owner.Capacity = totalItemsCount.Value;
return memoryBlocks;
}
public RecyclableList(int blockSize = RecyclableDefaults.BlockSize, long? totalItemsCount = default)
{
_blockSize = blockSize;
_memoryBlocks = SetupMemoryBlocks(this, blockSize, totalItemsCount);
}
public RecyclableList(IEnumerable<T> source, int blockSize = RecyclableDefaults.BlockSize, long? totalItemsCount = default)
{
_blockSize = blockSize;
_memoryBlocks = SetupMemoryBlocks(this, blockSize, totalItemsCount);
AddRange(this, source);
}
public T this[long index]
{
get => _memoryBlocks[(int)(index / _blockSize)][index % _blockSize];
set => _memoryBlocks[(int)(index / _blockSize)][index % _blockSize] = value;
}
public T this[int index]
{
get => _memoryBlocks[index / _blockSize][index % _blockSize];
set => _memoryBlocks[index / _blockSize][index % _blockSize] = value;
}
public long Capacity { get; protected set; }
public int Count => (int)LongCount;
public long LongCount { get; set; }
public bool IsReadOnly { get; } = false;
public int BlockCount => _memoryBlocks.Count;
public void Add(T item)
{
long oldCount = LongCount;
int blockSize = _blockSize;
if (oldCount == Capacity)
{
AddCapacity(this, _memoryBlocks, blockSize);
}
_memoryBlocks[(int)(oldCount / blockSize)][oldCount % blockSize] = item;
LongCount++;
}
public void Clear()
{
try
{
// Remove in reversed order for performance savings
while (_memoryBlocks.Count > 0)
while (_memoryBlocks.Length > 0)
{
try
{
RemoveBlock(this, _memoryBlocks, _blockSize, _memoryBlocks.Count - 1);
_memoryBlocks = RemoveBlock(this, _memoryBlocks, _blockSize, _memoryBlocks.Length - 1);
}
catch (Exception)
{
@@ -154,36 +304,77 @@ namespace Recyclable.Collections
}
finally
{
LongCount = 0;
_memoryBlocks.Clear();
_longCount = 0;
}
}
public bool Contains(T item) => _memoryBlocks.Contains(item);
public bool Contains(T item) => _memoryBlocks.Any(x => x.Contains(item));
public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, (int)LongCount % _blockSize, array, arrayIndex);
public IEnumerator<T> GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator();
public int IndexOf(T item) => (int)_memoryBlocks.LongIndexOf(_blockSize, item, _equalityComparer);
public int IndexOf(T item) => (int)_memoryBlocks.LongIndexOf(_longCount, item, _equalityComparer);
public void Insert(int index, T item) => throw new NotSupportedException();
public long LongIndexOf(T item) => _memoryBlocks.LongIndexOf(_blockSize, item, _equalityComparer);
public bool Remove(T item) => throw new NotSupportedException();
public void RemoveBlock(int index) => _memoryBlocks = RemoveBlock(this, _memoryBlocks, _blockSize, index);
public void RemoveAt(int index) => RemoveAt(this, index);
public void RemoveAt(long index) => RemoveAt(this, index);
IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator();
public void Dispose()
//private static void AddCapacity(RecyclableList<T> list, RecyclableArrayList<T[]> memoryBlocks, int additionalCapacity)
//{
// T[] newMemoryBlock = RentArrayFromPool(additionalCapacity);
// memoryBlocks.Add(newMemoryBlock);
// list.Capacity += additionalCapacity;
//}
private static T[][] RemoveBlock(RecyclableList<T> owner, in T[][] memoryBlocks, int blockSize, int blockIndex)
{
Clear();
GC.SuppressFinalize(this);
try
{
var memoryBlocksSpan = new Span<T[]>(memoryBlocks);
return SetNewLength(memoryBlocks, blockSize, memoryBlocks.Length - 1);
}
finally
{
owner._capacity -= blockSize;
}
}
public void RemoveBlock(int index) => RemoveBlock(this, _memoryBlocks, _blockSize, index);
//private static RecyclableArrayList<T[]> SetupMemoryBlocks(RecyclableList<T> owner, int blockSize, long? totalItemsCount)
//{
// if (blockSize <= 0)
// {
// return new();
// }
// totalItemsCount ??= 8 * blockSize;
// int additionalArray = totalItemsCount % blockSize > 0 ? 1 : 0;
// int memoryBlockCount = (int)(totalItemsCount.Value / blockSize).LimitTo(int.MaxValue) + additionalArray;
// RecyclableArrayList<T[]> memoryBlocks = new(memoryBlockCount);
// for (var blockIdx = 0; blockIdx < memoryBlockCount; blockIdx++)
// {
// memoryBlocks[blockIdx] = RentArrayFromPool(blockSize);
// }
// memoryBlocks.Count = memoryBlockCount;
// owner.Capacity = totalItemsCount.Value;
// return memoryBlocks;
//}
public void Dispose()
{
if (_capacity > 0)
{
Clear();
_capacity = 0;
_memoryBlocks.ReturnToPool(_arrayPool);
GC.SuppressFinalize(this);
}
}
}
}
@@ -15,17 +15,17 @@ namespace Recyclable.Collections
}
}
public static void CopyTo<T>(this RecyclableList<T[]> arrays, long startingIndex, int blockSize, int lastBlockSize, T[] destinationArray, int destinationArrayIndex)
public static void CopyTo<T>(this T[][] arrays, long startingIndex, int blockSize, int lastBlockSize, T[] destinationArray, int destinationArrayIndex)
{
Span<T> arrayMemory = destinationArray.AsSpan();
arrayMemory = arrayMemory[destinationArrayIndex..];
int startingArrayIndex = (int)(startingIndex / blockSize);
startingIndex %= blockSize;
if (arrays.LongCount > 0)
if (arrays.LongLength > 0)
{
ReadOnlySpan<T> sourceMemory = arrays[0].AsSpan();
var maxToCopy = (int)Math.Min(blockSize - startingIndex, arrayMemory.Length);
if (arrays.LongCount == 1)
if (arrays.LongLength == 1)
{
maxToCopy = (int)Math.Min(maxToCopy, lastBlockSize);
}
@@ -35,7 +35,7 @@ namespace Recyclable.Collections
arrayMemory = arrayMemory[maxToCopy..];
}
for (var arrayIdx = startingArrayIndex + 1; arrayIdx < arrays.Count - 1 && arrayMemory.Length > 0; arrayIdx++)
for (var arrayIdx = startingArrayIndex + 1; arrayIdx < arrays.Length - 1 && arrayMemory.Length > 0; arrayIdx++)
{
ReadOnlySpan<T> sourceMemory = arrays[arrayIdx].AsSpan();
var maxToCopy = Math.Min(blockSize, arrayMemory.Length);
@@ -44,7 +44,7 @@ namespace Recyclable.Collections
arrayMemory = arrayMemory[maxToCopy..];
}
if (arrays.Count > 1 && arrayMemory.Length > 0)
if (arrays.Length > 1 && arrayMemory.Length > 0)
{
ReadOnlySpan<T> sourceMemory = arrays[^1].AsSpan();
var maxToCopy = Math.Min(lastBlockSize, arrayMemory.Length);
@@ -56,35 +56,35 @@ namespace Recyclable.Collections
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
//public static bool Contains<T>(this RecyclableList<T[]> arrays, T item) => arrays.Any(x => x.Contains(item));
//public static IEnumerable<T> Enumerate<T>(this RecyclableList<T[]> arrays, int chunkSize, long totalCount)
//{
// long currentCount = 0;
// for (var arrayIdx = 0; arrayIdx < arrays.Count; arrayIdx++)
// {
// var array = arrays[arrayIdx];
// currentCount += chunkSize;
// switch (currentCount < totalCount)
// {
// case true:
// for (var valueIdx = 0; valueIdx < chunkSize; valueIdx++)
// {
// yield return array[valueIdx];
// }
public static IEnumerable<T> Enumerate<T>(this T[][] arrays, int chunkSize, long totalCount)
{
long currentCount = 0;
for (var arrayIdx = 0; arrayIdx < arrays.Length && currentCount < totalCount; arrayIdx++)
{
var array = arrays[arrayIdx];
currentCount += chunkSize;
switch (currentCount < totalCount)
{
case true:
for (var valueIdx = 0; valueIdx < chunkSize; valueIdx++)
{
yield return array[valueIdx];
}
// break;
break;
// case false:
// var partialCount = (int)(totalCount % chunkSize);
// int maxCount = partialCount > 0 ? partialCount : chunkSize;
// for (var valueIdx = 0; valueIdx < maxCount; valueIdx++)
// {
// yield return array[valueIdx];
// }
case false:
var partialCount = (int)(totalCount % chunkSize);
int maxCount = partialCount > 0 ? partialCount : chunkSize;
for (var valueIdx = 0; valueIdx < maxCount; valueIdx++)
{
yield return array[valueIdx];
}
// break;
// }
// }
//}
break;
}
}
}
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
//public static T LimitTo<T>(this T value, T maxValue, IComparer<T> comparer)
@@ -107,14 +107,26 @@ namespace Recyclable.Collections
return -1;
}
public static long LongIndexOf<T>(this RecyclableArrayList<T> arrays, int itemsCount, T itemToFind, IEqualityComparer<T> comparer)
public static long LongIndexOf<T>(this T[][] arrays, long itemsCount, T itemToFind, IEqualityComparer<T> comparer)
{
for (var itemIdx = 0; itemIdx < itemsCount; itemIdx++)
var arraysSpan = new Span<T[]>(arrays);
var arraysCount = arraysSpan.Length;
for (int arrayIdx = 0, scannedCount = 0; arrayIdx < arraysCount; arrayIdx++)
{
var item = arrays[itemIdx];
if (comparer.Equals(itemToFind, item))
var itemsSpan = new Span<T>(arraysSpan[arrayIdx]);
var arrayItemsCount = itemsSpan.Length;
for (var itemIdx = 0; itemIdx < arrayItemsCount; itemIdx++)
{
return itemIdx;
if (comparer.Equals(itemToFind, itemsSpan[itemIdx]))
{
return scannedCount;
}
scannedCount++;
if (scannedCount == itemsCount)
{
return -1;
}
}
}
+1 -1
View File
@@ -47,7 +47,7 @@ namespace Recyclable.Collections
{
var toRemove = List[LongCount - 1];
LongCount--;
if (List.Capacity - LongCount == _blockSize)
if (List.Capacity * _blockSize - LongCount == _blockSize)
{
List.RemoveBlock(List.BlockCount - 1);
}
@@ -156,7 +156,7 @@ namespace Recyclable.CollectionsTests
using var list = NewRecyclableList;
// Act
_ = Assert.Throws<NotSupportedException>(() => list.RemoveAt(0));
_ = Assert.Throws<ArgumentOutOfRangeException>(() => list.RemoveAt(0));
}
[Fact]