Merge branch 'recyclable-block-list-added' of https://github.com/mlemanczyk/Recyclable.Collections into recyclable-block-list-added
This commit is contained in:
commit
360f1c9880
12 files changed
+687
-261
No files matched your search
@@ -41,12 +41,12 @@ This is the direct equivalent of `List<T>` class, except that the arrays are tak
|
||||
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 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.
|
||||
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 proven to perform worse. For this reason, properties & methods expected to be on the hot-path, duplicate much of the code 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.
|
||||
## 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. The protected stuff is there for your use.
|
||||
|
||||
If you desire to have a common base for calls, each of the classes implements `IList<T>` & `ILongList<T>` interfaces. `ILongList<T>` is a new interface, made alike `IList<T>`, but unlocking full support for `long` indexing & use. Considering the characteristics of the classes I feel that the generic name is justified, instead of `IRecyclableList<T>` what you could expect.
|
||||
If you desire to have a common base for calls, each of the classes implements `IList<T>` & `ILongList<T>` interfaces. `ILongList<T>` is a new interface, made alike `IList<T>`, but unlocking full support for `long` indexing & use. Considering the characteristics of the classes I feel that the generic name is justified, instead of `IRecyclableList<T>` what you could expect.
|
||||
|
||||
## Protection against inccorrect use & state
|
||||
Having protection against invalid state of the object is an important part of software engineering. The classes included in this project provide the same level of protection as the regular `array` & `List` class. They will raise exceptions when invalid index is given. But there is no additional protection that the content & state of the objects internally used by these classes are valid. You can break them, if you want to.
|
||||
@@ -62,19 +62,32 @@ This is by design, for the best performance. Initially there have been checks in
|
||||
|
||||
If you foresee the overflowing as an issue, you're welcomed to propose an improvement & file a PR with proposed solution.
|
||||
|
||||
# Auto-properties & new language features
|
||||
When you look at the code you may note, that it looks like in early c# days. Where the new language features yield the same or better performance, they are & will be used.
|
||||
|
||||
However, many of the new language features have proved to provide worse performance at some point in my testing. It includes auto-properties, for which I could see getters & setters generated in IL, instead of fields. Because all the classes are expected to be on the hot-paths, I've eliminated most of them.
|
||||
|
||||
If you know & can show it in your benchmarks that the code can be simplified without impacting the performance, feel free to file a PR with proposed changes. Please include benchmark results from your testing to speed up things.
|
||||
|
||||
# Thread Safety
|
||||
The `public static` members of the classes are thread safe. Any instance members are not guaraneteed to be thread safe. You need to implement locking mechanism to safely use the classes in a multi-threading environment.
|
||||
|
||||
# Use
|
||||
All the classes included in this package are meant to be direct replacements of their corresponding system classes.
|
||||
* `IList<T>` ⇨ `ILongList<T>`
|
||||
* `List<T>` ⇨ `RecyclableList<T>`
|
||||
* `SortedList<T>` ⇨ `RecyclableSortedList<T>`
|
||||
* `Queue<T>` ⇨ `RecyclableQueue<T>`
|
||||
* `PriorityQueue<T>` ⇨ `RecyclableSortedList<T>`
|
||||
* `SortedList<T>` ⇨ `RecyclableSortedList<T>`
|
||||
* `Stack<T>` ⇨ `RecyclableStack<T>`
|
||||
* `IList<T>` ⇨ `ILongList<T>`
|
||||
|
||||
Examples of each of the above classes are provided below. Please refer automated tests for more.
|
||||
|
||||
## `RecyclableArrayList<T>`
|
||||
It's the closest equivalent to `List<T>` in regards to functionality. It supports items' removal & delivers the best performance. But it's limited to `int` range, tough. If you find that acceptable, my recommendation is to use this class as the direct replacement for `List<T>`.
|
||||
|
||||
Do note, that it implements `ILongList<T>` interface, too. This is to allow having common code base regardless, if you use the basic `RecyclableArrayList<T>` or it's `long`-range version - `RecyclableList<T>`. By doing that you can switch between them with highly limited changes in code.
|
||||
|
||||
## `RecyclableList<T>`
|
||||
|
||||
```CSharp
|
||||
@@ -110,4 +123,10 @@ public void RecyclableListExample()
|
||||
|
||||
list.Clear();
|
||||
}
|
||||
```
|
||||
```
|
||||
## `RecyclableArrayList<T>`
|
||||
|
||||
## `RecyclableArrayList<T>`
|
||||
|
||||
## `RecyclableArrayList<T>`
|
||||
|
||||
@@ -21,6 +21,8 @@ namespace Recyclable.Collections.Benchmarks
|
||||
|
||||
protected RecyclableArrayList<object>? _testObjectsAsRecyclableArrayList;
|
||||
protected RecyclableArrayList<object> TestObjectsAsRecyclableArrayList => _testObjectsAsRecyclableArrayList ?? throw new NullReferenceException("Something is wrong and the field is not initialized");
|
||||
protected RecyclableList<object>? _testObjectsAsRecyclableList;
|
||||
protected RecyclableList<object> TestObjectsAsRecyclableList => _testObjectsAsRecyclableList ?? throw new NullReferenceException("Something is wrong and the field is not initialized");
|
||||
protected IEnumerable<object> TestObjectsAsIEnumerable => Enumerable.Range(1, TestObjectCount).Select(static x => new object());
|
||||
|
||||
protected static void DoNothing<T>(T obj)
|
||||
|
||||
@@ -1,16 +1,50 @@
|
||||
using BenchmarkDotNet.Running;
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Running;
|
||||
using Recyclable.Collections.Benchmarks;
|
||||
|
||||
//BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(new[] { "-f*" });
|
||||
|
||||
//BenchmarkRunner.Run<RecyclableCollectionsBenchmarks>();
|
||||
//BenchmarkRunner.Run<RefVsInstanceMemberBenchmarks>();
|
||||
|
||||
var benchmark = new RecyclableCollectionsBenchmarks();
|
||||
benchmark.Setup();
|
||||
//benchmark.RecyclableArrayList_AddRange_WithCapacity();
|
||||
//benchmark.RecyclableArrayList_Add_WithCapacity();
|
||||
benchmark.RecyclableList_Add_WithCapacity();
|
||||
|
||||
|
||||
internal class Program
|
||||
{
|
||||
void RunBenchmark()
|
||||
{
|
||||
var benchmark = new RecyclableCollectionsBenchmarks();
|
||||
benchmark.Setup();
|
||||
foreach (var _ in Enumerable.Range(1, 1000))
|
||||
{
|
||||
//benchmark.RecyclableList_Contains();
|
||||
//benchmark.RecyclableArrayList_AddRange_WithCapacity();
|
||||
//benchmark.RecyclableArrayList_Add_WithCapacity();
|
||||
|
||||
//benchmark.RecyclableList_Create();
|
||||
//benchmark.RecyclableList_Create_WithCapacity();
|
||||
//benchmark.RecyclableList_Add();
|
||||
//benchmark.RecyclableList_Add_WithCapacity();
|
||||
}
|
||||
|
||||
benchmark.Cleanup();
|
||||
}
|
||||
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
BenchmarkRunner.Run<RecyclableCollectionsBenchmarks>(
|
||||
ManualConfig.Create(DefaultConfig.Instance).WithOptions(ConfigOptions.DisableOptimizationsValidator | ConfigOptions.JoinSummary)
|
||||
);
|
||||
|
||||
//RunBenchmark();
|
||||
}
|
||||
}
|
||||
// benchmark.Setup();
|
||||
// benchmark.Cleanup();
|
||||
|
||||
//benchmark.Setup();
|
||||
//benchmark.Cleanup();
|
||||
|
||||
//}
|
||||
|
||||
|
||||
//BenchmarkRunner.Run<DelegateVsComparerBenchmark>();
|
||||
|
||||
@@ -111,5 +111,53 @@ namespace Recyclable.Collections.Benchmarks
|
||||
var data = TestObjectsAsList;
|
||||
DoNothing(data.Count);
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public void List_Contains_1_000_FirstItems()
|
||||
{
|
||||
var data = TestObjects;
|
||||
var list = TestObjectsAsList;
|
||||
var dataCount = 1_000;
|
||||
for (var i = 0; i < dataCount; i++)
|
||||
{
|
||||
DoNothing(list.Contains(data[i]));
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void List_Contains_1_000_LastItems()
|
||||
{
|
||||
var data = TestObjects;
|
||||
var list = TestObjectsAsList;
|
||||
var dataCount = 1_000;
|
||||
for (var i = 0; i < dataCount; i++)
|
||||
{
|
||||
DoNothing(list.Contains(data[^(i + 1)]));
|
||||
}
|
||||
}
|
||||
|
||||
//[Benchmark(Baseline = true)]
|
||||
public void List_IndexOf_1_000_FirstItems()
|
||||
{
|
||||
var data = TestObjects;
|
||||
var list = TestObjectsAsList;
|
||||
var dataCount = 1_000;
|
||||
for (var i = 0; i < dataCount; i++)
|
||||
{
|
||||
DoNothing(list.IndexOf(data[i]));
|
||||
}
|
||||
}
|
||||
|
||||
//[Benchmark]
|
||||
public void List_IndexOf_1_000_LastItems()
|
||||
{
|
||||
var data = TestObjects;
|
||||
var list = TestObjectsAsList;
|
||||
var dataCount = 1_000;
|
||||
for (var i = 0; i < dataCount; i++)
|
||||
{
|
||||
DoNothing(list.IndexOf(data[^(i + 1)]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -4,21 +4,21 @@ namespace Recyclable.Collections.Benchmarks
|
||||
{
|
||||
public partial class RecyclableCollectionsBenchmarks : BenchmarkBase
|
||||
{
|
||||
[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);
|
||||
DoNothing(list);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
//[Benchmark]
|
||||
public void RecyclableArrayList_Add()
|
||||
{
|
||||
var data = TestObjects;
|
||||
@@ -30,7 +30,7 @@ namespace Recyclable.Collections.Benchmarks
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
//[Benchmark]
|
||||
public void RecyclableArrayList_Add_WithCapacity()
|
||||
{
|
||||
var data = TestObjects;
|
||||
|
||||
+54
-6
@@ -4,21 +4,21 @@ namespace Recyclable.Collections.Benchmarks
|
||||
{
|
||||
public partial class RecyclableCollectionsBenchmarks : BenchmarkBase
|
||||
{
|
||||
[Benchmark]
|
||||
//[Benchmark]
|
||||
public void RecyclableList_Create()
|
||||
{
|
||||
using var list = new RecyclableList<object>();
|
||||
DoNothing(list);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
//[Benchmark]
|
||||
public void RecyclableList_Create_WithCapacity()
|
||||
{
|
||||
using var list = new RecyclableList<object>(initialCapacity: TestObjectCount);
|
||||
using var list = new RecyclableList<object>(minBlockSize: 102_400, expectedItemsCount: TestObjectCount);
|
||||
DoNothing(list);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
//[Benchmark]
|
||||
public void RecyclableList_Add()
|
||||
{
|
||||
var data = TestObjects;
|
||||
@@ -30,12 +30,12 @@ namespace Recyclable.Collections.Benchmarks
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
//[Benchmark]
|
||||
public void RecyclableList_Add_WithCapacity()
|
||||
{
|
||||
var data = TestObjects;
|
||||
var dataCount = TestObjectCount;
|
||||
using var list = new RecyclableList<object>(dataCount);
|
||||
using var list = new RecyclableList<object>(minBlockSize: 102_400, expectedItemsCount: dataCount);
|
||||
for (var i = 0; i < dataCount; i++)
|
||||
{
|
||||
list.Add(data[i]);
|
||||
@@ -77,5 +77,53 @@ namespace Recyclable.Collections.Benchmarks
|
||||
var data = _testRecyclableList;
|
||||
DoNothing(data.LongCount);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void RecyclableList_Contains_1_000_FirstItems()
|
||||
{
|
||||
var data = TestObjects;
|
||||
var list = TestObjectsAsRecyclableList;
|
||||
var dataCount = 1000;
|
||||
for (var i = 0; i < dataCount; i++)
|
||||
{
|
||||
DoNothing(list.Contains(data[i]));
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void RecyclableList_Contains_1_000_LastItems()
|
||||
{
|
||||
var data = TestObjects;
|
||||
var list = TestObjectsAsRecyclableList;
|
||||
var dataCount = 1_000;
|
||||
for (var i = 0; i < dataCount; i++)
|
||||
{
|
||||
DoNothing(list.Contains(data[^(i + 1)]));
|
||||
}
|
||||
}
|
||||
|
||||
//[Benchmark]
|
||||
public void RecyclableList_IndexOf_1_000_FirstItems()
|
||||
{
|
||||
var data = TestObjects;
|
||||
var list = TestObjectsAsRecyclableList;
|
||||
var dataCount = 1000;
|
||||
for (var i = 0; i < dataCount; i++)
|
||||
{
|
||||
DoNothing(list.IndexOf(data[i]));
|
||||
}
|
||||
}
|
||||
|
||||
//[Benchmark]
|
||||
public void RecyclableList_IndexOf_1_000_LastItems()
|
||||
{
|
||||
var data = TestObjects;
|
||||
var list = TestObjectsAsRecyclableList;
|
||||
var dataCount = 1_000;
|
||||
for (var i = 0; i < dataCount; i++)
|
||||
{
|
||||
DoNothing(list.IndexOf(data[^(i + 1)]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,4 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Recyclable.Collections.Benchmarks
|
||||
{
|
||||
@@ -18,11 +13,12 @@ namespace Recyclable.Collections.Benchmarks
|
||||
{
|
||||
Console.WriteLine("******* GLOBAL SETUP RAISED *******");
|
||||
_testObjects = Enumerable.Range(1, TestObjectCount).Select(x => new object()).ToArray();
|
||||
_testRecyclableArrayList = new(TestObjects, TestObjectCount);
|
||||
_testRecyclableList = new(TestObjects, TestObjectCount);
|
||||
_testRecyclableArrayList = new(TestObjects, initialCapacity: TestObjectCount);
|
||||
_testRecyclableList = new(TestObjects, expectedItemsCount: TestObjectCount);
|
||||
_testArray = TestObjects.ToArray();
|
||||
_testObjectsAsList = TestObjects.ToList();
|
||||
_testObjectsAsRecyclableArrayList = TestObjects.ToRecyclableArrayList();
|
||||
_testObjectsAsRecyclableList = TestObjects.ToRecyclableList();
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
@@ -33,6 +29,7 @@ namespace Recyclable.Collections.Benchmarks
|
||||
_testRecyclableList?.Dispose();
|
||||
TestObjectsAsRecyclableArrayList?.Dispose();
|
||||
_testObjectsAsRecyclableArrayList = default;
|
||||
_testObjectsAsRecyclableList = default;
|
||||
_testObjectsAsList = default;
|
||||
_testObjects = default;
|
||||
}
|
||||
|
||||
@@ -21,27 +21,22 @@ namespace Recyclable.Collections
|
||||
{
|
||||
ArrayPool<T> arrayPool = _arrayPool;
|
||||
var sourceLength = source?.Length ?? 0;
|
||||
T[] newMemoryBlock = newSize.RentArrayFromPool(arrayPool);
|
||||
switch (newSize >= sourceLength)
|
||||
T[] newMemoryBlock = newSize >= RecyclableDefaults.MinPooledArrayLength
|
||||
? arrayPool.Rent(newSize)
|
||||
: new T[newSize];
|
||||
|
||||
if (sourceLength > 0)
|
||||
{
|
||||
case true:
|
||||
if (sourceLength > 0)
|
||||
{
|
||||
Memory<T> sourceMemory = new(source);
|
||||
Memory<T> newArrayMemory = new(newMemoryBlock);
|
||||
sourceMemory.CopyTo(newArrayMemory);
|
||||
source!.ReturnToPool(arrayPool);
|
||||
}
|
||||
|
||||
return newMemoryBlock;
|
||||
|
||||
case false:
|
||||
var sourceSpan = new Span<T>(source)[..newSize];
|
||||
Span<T> newArraySpan = new(newMemoryBlock);
|
||||
sourceSpan.CopyTo(newArraySpan);
|
||||
source!.ReturnToPool(arrayPool);
|
||||
return newMemoryBlock;
|
||||
var sourceSpan = new Memory<T>(source);
|
||||
var newArraySpan = new Memory<T>(newMemoryBlock);
|
||||
sourceSpan.CopyTo(newArraySpan);
|
||||
if (sourceLength >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
arrayPool.Return(source!);
|
||||
}
|
||||
}
|
||||
|
||||
return newMemoryBlock;
|
||||
}
|
||||
|
||||
protected int EnsureCapacity(in int requestedCapacity)
|
||||
@@ -153,7 +148,7 @@ namespace Recyclable.Collections
|
||||
|
||||
public bool IsReadOnly { get; } = false;
|
||||
|
||||
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(T item)
|
||||
{
|
||||
int requiredCapacity = _count + 1;
|
||||
@@ -247,41 +242,41 @@ namespace Recyclable.Collections
|
||||
return;
|
||||
}
|
||||
|
||||
int capacity = _capacity;
|
||||
int targetIndex = _count;
|
||||
int targetItemIdx = _count;
|
||||
Span<T> memorySpan;
|
||||
if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity))
|
||||
{
|
||||
_ = EnsureCapacity(targetIndex + requiredAdditionalCapacity);
|
||||
_ = EnsureCapacity(targetItemIdx + requiredAdditionalCapacity);
|
||||
memorySpan = new(_memoryBlock);
|
||||
foreach (var item in source)
|
||||
{
|
||||
memorySpan[targetIndex++] = item;
|
||||
memorySpan[targetItemIdx++] = item;
|
||||
}
|
||||
|
||||
_count = targetIndex;
|
||||
_count = targetItemIdx;
|
||||
return;
|
||||
}
|
||||
|
||||
int i;
|
||||
using var enumerator = source.GetEnumerator();
|
||||
|
||||
int capacity = _capacity;
|
||||
memorySpan = new(_memoryBlock);
|
||||
if (enumerator.MoveNext())
|
||||
{
|
||||
int available = capacity - targetIndex;
|
||||
int available = capacity - targetItemIdx;
|
||||
while (true)
|
||||
{
|
||||
if (targetIndex + growByCount > capacity)
|
||||
if (targetItemIdx + growByCount > capacity)
|
||||
{
|
||||
capacity = EnsureCapacity(capacity + growByCount);
|
||||
memorySpan = new(_memoryBlock);
|
||||
available = capacity - targetIndex;
|
||||
available = capacity - targetItemIdx;
|
||||
}
|
||||
|
||||
for (i = 0; i < available; i++)
|
||||
{
|
||||
memorySpan[targetIndex++] = enumerator.Current;
|
||||
memorySpan[targetItemIdx++] = enumerator.Current;
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
break;
|
||||
@@ -295,7 +290,7 @@ namespace Recyclable.Collections
|
||||
}
|
||||
}
|
||||
|
||||
_count = targetIndex;
|
||||
_count = targetItemIdx;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
{
|
||||
public class RecyclableDefaults
|
||||
{
|
||||
public const int BlockSize = 10_240;
|
||||
public const int BlockSize = 16_384;
|
||||
public const int Capacity = 1;
|
||||
public const int MinPooledArrayLength = 100;
|
||||
public const int MinPooledArrayLength = 128;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Buffers;
|
||||
using System.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
@@ -7,11 +6,18 @@ 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 const int ItemNotFoundIndex = -1;
|
||||
private static readonly ArrayPool<T[]> _defaultMemoryBlocksPool = ArrayPool<T[]>.Create();
|
||||
private static readonly ArrayPool<T> _defaultBlockArrayPool = ArrayPool<T>.Create();
|
||||
private static readonly T[][] _emptyMemoryBlocksArray = new T[0][];
|
||||
private static readonly T[] _emptyBlockArray = new T[0];
|
||||
private static readonly IEqualityComparer<T> _equalityComparer = EqualityComparer<T>.Default;
|
||||
private readonly int _blockSize;
|
||||
|
||||
private int _blockSize;
|
||||
private int _lastBlockIndex;
|
||||
private int _nextItemIndex;
|
||||
protected ArrayPool<T[]> _memoryBlocksPool;
|
||||
protected ArrayPool<T> _blockArrayPool;
|
||||
protected T[][] _memoryBlocks;
|
||||
|
||||
private long _capacity;
|
||||
@@ -21,7 +27,7 @@ namespace Recyclable.Collections
|
||||
protected set => _capacity = value;
|
||||
}
|
||||
|
||||
public int Count => (int)LongCount;
|
||||
public int Count => (int)_longCount;
|
||||
|
||||
protected long _longCount;
|
||||
public long LongCount
|
||||
@@ -30,13 +36,12 @@ namespace Recyclable.Collections
|
||||
set => _longCount = value;
|
||||
}
|
||||
|
||||
public bool IsReadOnly { get; } = false;
|
||||
public bool IsReadOnly { get; }
|
||||
public int BlockCount => _memoryBlocks.Length;
|
||||
|
||||
private static void RemoveAt(RecyclableList<T> list, long index)
|
||||
private static void RemoveAt(RecyclableList<T> list, long index, ArrayPool<T> blockArrayPool)
|
||||
{
|
||||
long oldCount = list._longCount;
|
||||
long oldCountMinus1 = oldCount - 1;
|
||||
long oldCountMinus1 = list._longCount - 1;
|
||||
if (index != oldCountMinus1)
|
||||
{
|
||||
ThrowArgumentOutOfRangeException();
|
||||
@@ -44,10 +49,25 @@ namespace Recyclable.Collections
|
||||
|
||||
list._longCount--;
|
||||
int blockSize = list._blockSize;
|
||||
if (list._nextItemIndex > 0)
|
||||
{
|
||||
list._nextItemIndex--;
|
||||
}
|
||||
else
|
||||
{
|
||||
list._lastBlockIndex--;
|
||||
list._nextItemIndex = blockSize;
|
||||
}
|
||||
|
||||
if ((list._capacity * blockSize) - oldCountMinus1 == blockSize)
|
||||
{
|
||||
T[][] memoryBlocks = list._memoryBlocks;
|
||||
ReleaseBlock(list, memoryBlocks, blockSize, memoryBlocks.Length - 1);
|
||||
if (blockSize >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
blockArrayPool.Return(memoryBlocks[list.BlockCount - 1]);
|
||||
}
|
||||
|
||||
list._capacity -= blockSize;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,124 +77,155 @@ 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, int minBlockSize, in long newCapacity, ArrayPool<T[]> memoryBlocksPool, ArrayPool<T> blockArrayPool)
|
||||
{
|
||||
ArrayPool<T[]> arrayPool = _arrayPool;
|
||||
ArrayPool<T> blockArrayPool = _blockArrayPool;
|
||||
var sourceBlockCount = source?.Length ?? 0;
|
||||
int requiredBlockCount = (int)(newSize / blockSize) + (newSize % blockSize > 0 ? 1 : 0);
|
||||
if (requiredBlockCount == sourceBlockCount)
|
||||
const int sourceBlockCount = 0, requiredBlockCount = 1, uninitializedBlocksCount = 2, i = 3;
|
||||
|
||||
Span<int> localInts = stackalloc int[4]
|
||||
{
|
||||
return source!;
|
||||
source?.Length ?? 0, // sourceBlockCount
|
||||
(int)(newCapacity / minBlockSize) + (newCapacity % minBlockSize > 0 ? 1 : 0), // requiredBlockCount
|
||||
0, // uninitializedBlocksCount
|
||||
0, // i
|
||||
};
|
||||
|
||||
T[][] newMemoryBlocks = localInts[requiredBlockCount] < RecyclableDefaults.MinPooledArrayLength
|
||||
? new T[localInts[requiredBlockCount]][]
|
||||
: memoryBlocksPool.Rent(localInts[requiredBlockCount]);
|
||||
|
||||
Span<T[]> newMemoryBlocksSpan = new(newMemoryBlocks);
|
||||
if (localInts[sourceBlockCount] > 0)
|
||||
{
|
||||
Span<T[]> sourceSpan = new(source);
|
||||
sourceSpan.CopyTo(newMemoryBlocksSpan);
|
||||
if (localInts[sourceBlockCount] >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
memoryBlocksPool.Return(source!);
|
||||
}
|
||||
|
||||
newMemoryBlocksSpan = newMemoryBlocksSpan[localInts[sourceBlockCount]..];
|
||||
}
|
||||
|
||||
Span<T[]> newArraySpan;
|
||||
T[][] newMemoryBlocks = requiredBlockCount.RentArrayFromPool(arrayPool);
|
||||
switch (requiredBlockCount >= sourceBlockCount)
|
||||
localInts[uninitializedBlocksCount] = newMemoryBlocksSpan.Length;
|
||||
if (localInts[uninitializedBlocksCount] == localInts[requiredBlockCount])
|
||||
{
|
||||
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);
|
||||
}
|
||||
if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
newMemoryBlocksSpan[0] = blockArrayPool.Rent(minBlockSize);
|
||||
minBlockSize = newMemoryBlocksSpan[0].Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
newMemoryBlocksSpan[0] = new T[minBlockSize];
|
||||
}
|
||||
|
||||
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;
|
||||
newMemoryBlocksSpan = newMemoryBlocksSpan[1..];
|
||||
localInts[uninitializedBlocksCount]--;
|
||||
}
|
||||
|
||||
if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
for (localInts[i] = 0; localInts[i] < localInts[uninitializedBlocksCount]; localInts[i]++)
|
||||
{
|
||||
newMemoryBlocksSpan[localInts[i]] = blockArrayPool.Rent(minBlockSize);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (localInts[i] = 0; localInts[i] < localInts[uninitializedBlocksCount]; localInts[i]++)
|
||||
{
|
||||
newMemoryBlocksSpan[localInts[i]] = new T[minBlockSize];
|
||||
}
|
||||
}
|
||||
|
||||
return newMemoryBlocks;
|
||||
}
|
||||
|
||||
protected long EnsureCapacity(in long requestedCapacity)
|
||||
{
|
||||
long oldCapacity = _capacity;
|
||||
ref T[][] memory = ref _memoryBlocks;
|
||||
|
||||
T[][] memory = _memoryBlocks;
|
||||
long newCapacity;
|
||||
switch (oldCapacity > 0)
|
||||
|
||||
if (_capacity > 0)
|
||||
{
|
||||
case true:
|
||||
newCapacity = oldCapacity;
|
||||
while (newCapacity < requestedCapacity)
|
||||
{
|
||||
newCapacity *= 2;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case false:
|
||||
newCapacity = requestedCapacity;
|
||||
break;
|
||||
newCapacity = _capacity;
|
||||
while (newCapacity < requestedCapacity)
|
||||
{
|
||||
newCapacity *= 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
newCapacity = requestedCapacity;
|
||||
}
|
||||
|
||||
int blockSize = _blockSize;
|
||||
memory = SetNewLength(memory, blockSize, newCapacity);
|
||||
newCapacity = memory.Length * blockSize;
|
||||
memory = SetNewLength(memory, _blockSize, newCapacity, _memoryBlocksPool, _blockArrayPool);
|
||||
if (newCapacity > 0 && _capacity == 0)
|
||||
{
|
||||
_blockSize = memory[0].Length;
|
||||
}
|
||||
|
||||
newCapacity = memory.Length * _blockSize;
|
||||
_capacity = newCapacity;
|
||||
_memoryBlocks = memory;
|
||||
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
|
||||
public RecyclableList(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool<T[]>? memoryBlocksPool = default, ArrayPool<T>? blockArrayPool = default)
|
||||
{
|
||||
_blockSize = blockSize;
|
||||
if (initialCapacity > 0)
|
||||
memoryBlocksPool ??= _defaultMemoryBlocksPool;
|
||||
blockArrayPool ??= _defaultBlockArrayPool;
|
||||
_memoryBlocksPool = memoryBlocksPool;
|
||||
_blockArrayPool = blockArrayPool;
|
||||
|
||||
if (expectedItemsCount > 0)
|
||||
{
|
||||
_memoryBlocks = SetNewLength(_memoryBlocks, blockSize, initialCapacity.Value);
|
||||
_capacity = _memoryBlocks.Length * blockSize;
|
||||
_memoryBlocks = SetNewLength(_memoryBlocks, minBlockSize, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool);
|
||||
if (_memoryBlocks.Length > 0)
|
||||
{
|
||||
minBlockSize = _memoryBlocks[0].Length;
|
||||
}
|
||||
|
||||
_blockSize = minBlockSize;
|
||||
_capacity = _memoryBlocks.Length * minBlockSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
_memoryBlocks = _emptyBlockArray;
|
||||
_blockSize = minBlockSize;
|
||||
_memoryBlocks = _emptyMemoryBlocksArray;
|
||||
}
|
||||
}
|
||||
|
||||
#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
|
||||
public RecyclableList(IEnumerable<T> source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool<T[]>? memoryBlocksPool = default, ArrayPool<T>? blockArrayPool = default)
|
||||
{
|
||||
_blockSize = blockSize;
|
||||
memoryBlocksPool ??= _defaultMemoryBlocksPool;
|
||||
blockArrayPool ??= _defaultBlockArrayPool;
|
||||
_memoryBlocksPool = memoryBlocksPool;
|
||||
_blockArrayPool = blockArrayPool;
|
||||
if (expectedItemsCount > 0)
|
||||
{
|
||||
_memoryBlocks = SetNewLength(_memoryBlocks, blockSize, expectedItemsCount.Value);
|
||||
_capacity = _memoryBlocks.Length * blockSize;
|
||||
_memoryBlocks = SetNewLength(_memoryBlocks, minBlockSize, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool);
|
||||
if (_memoryBlocks.Length > 0)
|
||||
{
|
||||
minBlockSize = _memoryBlocks[0].Length;
|
||||
}
|
||||
|
||||
_blockSize = minBlockSize;
|
||||
_capacity = _memoryBlocks.Length * minBlockSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
_memoryBlocks = _emptyBlockArray;
|
||||
_blockSize = minBlockSize;
|
||||
_memoryBlocks = _emptyMemoryBlocksArray;
|
||||
}
|
||||
|
||||
AddRange(this, source);
|
||||
AddRange(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;
|
||||
set => new Span<T>(_memoryBlocks[(int)(index / _blockSize)])[(int)(index % _blockSize)] = value;
|
||||
}
|
||||
|
||||
public T this[int index]
|
||||
@@ -183,17 +234,23 @@ namespace Recyclable.Collections
|
||||
set => new Span<T>(_memoryBlocks[index / _blockSize])[index % _blockSize] = value;
|
||||
}
|
||||
|
||||
public T[][] MemoryBlocks { get => _memoryBlocks; }
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(T item)
|
||||
{
|
||||
int blockSize = _blockSize;
|
||||
long oldCount = _longCount;
|
||||
long requiredCapacity = oldCount + 1;
|
||||
if (_capacity < requiredCapacity)
|
||||
if (_capacity < _longCount + 1)
|
||||
{
|
||||
_ = EnsureCapacity(requiredCapacity);
|
||||
_ = EnsureCapacity(_longCount + 1);
|
||||
}
|
||||
|
||||
_memoryBlocks[_lastBlockIndex][_nextItemIndex++] = item;
|
||||
if (_nextItemIndex == _blockSize)
|
||||
{
|
||||
_lastBlockIndex++;
|
||||
_nextItemIndex = 0;
|
||||
}
|
||||
|
||||
_memoryBlocks[(int)(oldCount / blockSize)][oldCount % blockSize] = item;
|
||||
_longCount++;
|
||||
}
|
||||
|
||||
@@ -210,20 +267,28 @@ namespace Recyclable.Collections
|
||||
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++)
|
||||
Span<T[]> memoryBlocksSpan = new(_memoryBlocks);
|
||||
int memoryBlocksCount = memoryBlocksSpan.Length;
|
||||
Span<T> targetBlockSpan = new(memoryBlocksSpan[targetBlockIdx]);
|
||||
for (var i = 0L; i < sourceItemsCount; i++)
|
||||
{
|
||||
targetBlockSpan[targetItemIdx++] = items[i];
|
||||
if (targetItemIdx == blockSize)
|
||||
{
|
||||
targetBlockIdx++;
|
||||
targetBlockSpan = new Span<T>(memoryBlockSpan[targetBlockIdx]);
|
||||
if (targetBlockIdx == memoryBlocksCount)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
targetBlockSpan = new Span<T>(memoryBlocksSpan[targetBlockIdx]);
|
||||
targetItemIdx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
_longCount = targetCapacity;
|
||||
_lastBlockIndex = targetBlockIdx - 1;
|
||||
_nextItemIndex = targetItemIdx;
|
||||
}
|
||||
|
||||
public void AddRange(in List<T> items)
|
||||
@@ -239,20 +304,28 @@ namespace Recyclable.Collections
|
||||
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]);
|
||||
Span<T[]> memoryBlocksSpan = new(_memoryBlocks);
|
||||
var memoryBlocksCount = memoryBlocksSpan.Length;
|
||||
Span<T> targetBlockSpan = new(memoryBlocksSpan[targetBlockIdx]);
|
||||
for (int i = 0; i < sourceItemsCount; i++)
|
||||
{
|
||||
targetBlockSpan[targetItemIdx++] = items[i];
|
||||
if (targetItemIdx == blockSize)
|
||||
{
|
||||
targetBlockIdx++;
|
||||
targetBlockSpan = new Span<T>(memoryBlockSpan[targetBlockIdx]);
|
||||
if (targetBlockIdx == memoryBlocksCount)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
targetBlockSpan = new Span<T>(memoryBlocksSpan[targetBlockIdx]);
|
||||
targetItemIdx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
_longCount = targetCapacity;
|
||||
_lastBlockIndex = targetBlockIdx - 1;
|
||||
_nextItemIndex = targetItemIdx;
|
||||
}
|
||||
|
||||
public void AddRange(in IList<T> items)
|
||||
@@ -268,114 +341,292 @@ namespace Recyclable.Collections
|
||||
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]);
|
||||
Span<T[]> memoryBlocksSpan = new(_memoryBlocks);
|
||||
var memoryBlocksCount = memoryBlocksSpan.Length;
|
||||
Span<T> targetBlockSpan = new(memoryBlocksSpan[targetBlockIdx]);
|
||||
for (int i = 0; i < sourceItemsCount; i++)
|
||||
{
|
||||
targetBlockSpan[targetItemIdx++] = items[i];
|
||||
if (targetItemIdx == blockSize)
|
||||
{
|
||||
targetBlockIdx++;
|
||||
targetBlockSpan = new Span<T>(memoryBlockSpan[targetBlockIdx]);
|
||||
if (targetBlockIdx == memoryBlocksCount)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
targetBlockSpan = new Span<T>(memoryBlocksSpan[targetBlockIdx]);
|
||||
targetItemIdx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
_longCount = targetCapacity;
|
||||
_lastBlockIndex = targetBlockIdx - 1;
|
||||
_nextItemIndex = targetItemIdx;
|
||||
}
|
||||
|
||||
private static void AddRange(RecyclableList<T> destination, IEnumerable<T> source)
|
||||
private void AddRange(IEnumerable<T> 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<T> sourceList)
|
||||
{
|
||||
AddRange(sourceList);
|
||||
return;
|
||||
}
|
||||
|
||||
if (source is RecyclableArrayList<T> sourceRecyclableArrayList)
|
||||
{
|
||||
AddRange(sourceRecyclableArrayList);
|
||||
return;
|
||||
}
|
||||
|
||||
if (source is IList<T> sourceIList)
|
||||
{
|
||||
AddRange(sourceIList);
|
||||
return;
|
||||
}
|
||||
|
||||
long capacity = _capacity;
|
||||
long oldLongCount = _longCount;
|
||||
int blockSize = _blockSize;
|
||||
int targetItemIdx = (int)(oldLongCount % blockSize);
|
||||
int targetBlockIdx = (targetItemIdx / blockSize) + (targetItemIdx > 0 ? 1 : 0);
|
||||
|
||||
Span<T[]> memoryBlocksSpan;
|
||||
Span<T> blockArraySpan;
|
||||
if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity))
|
||||
{
|
||||
|
||||
int requiredCapacity = targetItemIdx + requiredAdditionalCapacity;
|
||||
if (capacity < requiredCapacity)
|
||||
{
|
||||
_ = EnsureCapacity(requiredCapacity);
|
||||
blockSize = _blockSize;
|
||||
targetBlockIdx = (targetItemIdx / blockSize) + (targetItemIdx > 0 ? 1 : 0);
|
||||
targetItemIdx = (int)(oldLongCount % blockSize);
|
||||
}
|
||||
|
||||
memoryBlocksSpan = new(_memoryBlocks);
|
||||
var memoryBlocksCount = memoryBlocksSpan.Length;
|
||||
blockArraySpan = new Span<T>(memoryBlocksSpan[targetBlockIdx]);
|
||||
foreach (var item in source)
|
||||
{
|
||||
blockArraySpan[targetItemIdx++] = item;
|
||||
if (targetItemIdx == blockSize)
|
||||
{
|
||||
targetBlockIdx++;
|
||||
if (targetBlockIdx == memoryBlocksCount)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
targetItemIdx = 0;
|
||||
blockArraySpan = new Span<T>(memoryBlocksSpan[targetBlockIdx]);
|
||||
}
|
||||
}
|
||||
|
||||
_longCount = (targetBlockIdx * blockSize) + targetItemIdx;
|
||||
_lastBlockIndex = targetBlockIdx;
|
||||
_nextItemIndex = 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;
|
||||
_lastBlockIndex = targetBlockIdx - 1;
|
||||
_nextItemIndex = targetItemIdx;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
try
|
||||
if (_blockSize >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
// Remove in reversed order for performance savings
|
||||
var toRemoveIdx = _memoryBlocks.Length - 1;
|
||||
while (toRemoveIdx >= 0)
|
||||
Span<T[]> memoryBlocksSpan = new(_memoryBlocks);
|
||||
int memoryBlocksCount = memoryBlocksSpan.Length;
|
||||
ArrayPool<T> blockArrayPool = _blockArrayPool;
|
||||
for (int toRemoveIdx = 0; toRemoveIdx < memoryBlocksCount; toRemoveIdx++)
|
||||
{
|
||||
try
|
||||
{
|
||||
ReleaseBlock(this, _memoryBlocks, _blockSize, toRemoveIdx);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// We want to try returning as many arrays, as possible, before
|
||||
// the list is cleared.
|
||||
}
|
||||
|
||||
toRemoveIdx--;
|
||||
blockArrayPool.Return(memoryBlocksSpan[toRemoveIdx]);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_longCount = 0;
|
||||
}
|
||||
|
||||
_capacity = 0;
|
||||
_lastBlockIndex = 0;
|
||||
_nextItemIndex = 0;
|
||||
_longCount = 0;
|
||||
}
|
||||
|
||||
public bool Contains(T item) => _memoryBlocks.Any(x => x.Contains(item));
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Contains(T item)
|
||||
{
|
||||
if (_longCount == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, (int)LongCount % _blockSize, array, arrayIndex);
|
||||
int itemIndex, blockIndex = 0, lastBlockIndex = _lastBlockIndex, blockSize = _blockSize;
|
||||
Span<T[]> memoryBlocksSpan = new(_memoryBlocks);
|
||||
while (blockIndex < lastBlockIndex)
|
||||
{
|
||||
itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize);
|
||||
if (itemIndex >= 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
blockIndex++;
|
||||
}
|
||||
|
||||
return Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _nextItemIndex) >= 0;
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, arrayIndex);
|
||||
|
||||
public IEnumerator<T> GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator();
|
||||
|
||||
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) => ReleaseBlock(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();
|
||||
|
||||
private static void ReleaseBlock(RecyclableList<T> owner, in T[][] memoryBlocks, int blockSize, int blockIndex)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int IndexOf(T item)
|
||||
{
|
||||
try
|
||||
if (_longCount == 0)
|
||||
{
|
||||
memoryBlocks[blockIndex].ReturnToPool(_blockArrayPool);
|
||||
return ItemNotFoundIndex;
|
||||
}
|
||||
finally
|
||||
|
||||
int itemIndex, blockIndex = 0, lastBlockIndex = _lastBlockIndex, blockSize = _blockSize;
|
||||
Span<T[]> memoryBlocksSpan = new(_memoryBlocks);
|
||||
while (blockIndex < lastBlockIndex)
|
||||
{
|
||||
owner._capacity -= blockSize;
|
||||
itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize);
|
||||
if (itemIndex >= 0)
|
||||
{
|
||||
return (blockIndex * blockSize) + itemIndex;
|
||||
}
|
||||
|
||||
blockIndex++;
|
||||
}
|
||||
|
||||
itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _nextItemIndex);
|
||||
return itemIndex >= 0
|
||||
? (blockIndex * blockSize) + itemIndex
|
||||
: ItemNotFoundIndex;
|
||||
}
|
||||
|
||||
public void Insert(int index, T item) => throw new NotSupportedException();
|
||||
public long LongIndexOf(T item)
|
||||
{
|
||||
if (_longCount == 0)
|
||||
{
|
||||
return ItemNotFoundIndex;
|
||||
}
|
||||
|
||||
int itemIndex, blockIndex = 0, lastBlockIndex = _lastBlockIndex, blockSize = _blockSize;
|
||||
Span<T[]> memoryBlocksSpan = new(_memoryBlocks);
|
||||
while (blockIndex < lastBlockIndex)
|
||||
{
|
||||
itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize);
|
||||
if (itemIndex >= 0)
|
||||
{
|
||||
return ((long)blockIndex * blockSize) + itemIndex;
|
||||
}
|
||||
|
||||
blockIndex++;
|
||||
}
|
||||
|
||||
itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _nextItemIndex);
|
||||
return itemIndex >= 0
|
||||
? ((long)blockIndex * blockSize) + itemIndex
|
||||
: ItemNotFoundIndex;
|
||||
}
|
||||
|
||||
public bool Remove(T item) => throw new NotSupportedException();
|
||||
public void RemoveBlock(int index)
|
||||
{
|
||||
if (_blockSize < RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
_memoryBlocks[index] = _emptyBlockArray;
|
||||
}
|
||||
else
|
||||
{
|
||||
_blockArrayPool.Return(_memoryBlocks[index]);
|
||||
}
|
||||
|
||||
_capacity -= _blockSize;
|
||||
if (_lastBlockIndex == index)
|
||||
{
|
||||
_nextItemIndex = 0;
|
||||
}
|
||||
else if (_lastBlockIndex > index)
|
||||
{
|
||||
_lastBlockIndex--;
|
||||
}
|
||||
}
|
||||
|
||||
//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 RemoveAt(int index) => RemoveAt(this, index, _blockArrayPool);
|
||||
public void RemoveAt(long index) => RemoveAt(this, index, _blockArrayPool);
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_capacity > 0)
|
||||
{
|
||||
Clear();
|
||||
_capacity = 0;
|
||||
_memoryBlocks.ReturnToPool(_arrayPool);
|
||||
if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
_memoryBlocksPool.Return(_memoryBlocks);
|
||||
}
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,41 +15,26 @@ namespace Recyclable.Collections
|
||||
}
|
||||
}
|
||||
|
||||
public static void CopyTo<T>(this T[][] arrays, long startingIndex, int blockSize, int lastBlockSize, T[] destinationArray, int destinationArrayIndex)
|
||||
public static void CopyTo<T>(this T[][] sourceMemoryBlocks, long startingIndex, int blockSize, long itemsCount, T[] destinationArray, int destinationArrayIndex)
|
||||
{
|
||||
Span<T> arrayMemory = destinationArray.AsSpan();
|
||||
arrayMemory = arrayMemory[destinationArrayIndex..];
|
||||
int startingArrayIndex = (int)(startingIndex / blockSize);
|
||||
startingIndex %= blockSize;
|
||||
if (arrays.LongLength > 0)
|
||||
Memory<T> sourceBlockMemory;
|
||||
Memory<T> destinationArrayMemory;
|
||||
int startingBlockIndex = (int)(startingIndex / blockSize);
|
||||
int lastBlockIndex = (int)((itemsCount / blockSize) + (itemsCount - (itemsCount / blockSize) > 0 ? 1 : 0)) - 1;
|
||||
Span<T[]> sourceMemoryBlocksSpan = new(sourceMemoryBlocks, startingBlockIndex, lastBlockIndex - startingBlockIndex + 1);
|
||||
destinationArrayMemory = new Memory<T>(destinationArray, destinationArrayIndex, destinationArray.Length);
|
||||
int memoryBlockIndex;
|
||||
for (memoryBlockIndex = 0; memoryBlockIndex < lastBlockIndex; memoryBlockIndex++)
|
||||
{
|
||||
ReadOnlySpan<T> sourceMemory = arrays[0].AsSpan();
|
||||
var maxToCopy = (int)Math.Min(blockSize - startingIndex, arrayMemory.Length);
|
||||
if (arrays.LongLength == 1)
|
||||
{
|
||||
maxToCopy = (int)Math.Min(maxToCopy, lastBlockSize);
|
||||
}
|
||||
|
||||
sourceMemory = sourceMemory[(int)startingIndex..maxToCopy];
|
||||
sourceMemory.CopyTo(arrayMemory);
|
||||
arrayMemory = arrayMemory[maxToCopy..];
|
||||
sourceBlockMemory = new(sourceMemoryBlocksSpan[memoryBlockIndex], 0, blockSize);
|
||||
sourceBlockMemory.CopyTo(destinationArrayMemory);
|
||||
destinationArrayMemory = destinationArrayMemory[blockSize..];
|
||||
}
|
||||
|
||||
for (var arrayIdx = startingArrayIndex + 1; arrayIdx < arrays.Length - 1 && arrayMemory.Length > 0; arrayIdx++)
|
||||
if (itemsCount % blockSize > 0)
|
||||
{
|
||||
ReadOnlySpan<T> sourceMemory = arrays[arrayIdx].AsSpan();
|
||||
var maxToCopy = Math.Min(blockSize, arrayMemory.Length);
|
||||
sourceMemory = sourceMemory[..maxToCopy];
|
||||
sourceMemory.CopyTo(arrayMemory);
|
||||
arrayMemory = arrayMemory[maxToCopy..];
|
||||
}
|
||||
|
||||
if (arrays.Length > 1 && arrayMemory.Length > 0)
|
||||
{
|
||||
ReadOnlySpan<T> sourceMemory = arrays[^1].AsSpan();
|
||||
var maxToCopy = Math.Min(lastBlockSize, arrayMemory.Length);
|
||||
sourceMemory = sourceMemory[..maxToCopy];
|
||||
sourceMemory.CopyTo(arrayMemory);
|
||||
sourceBlockMemory = new(sourceMemoryBlocksSpan[^1], 0, (int)(itemsCount % blockSize));
|
||||
sourceBlockMemory.CopyTo(destinationArrayMemory);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Recyclable.CollectionsTests
|
||||
private const int _totalObjectCount = 5_000;
|
||||
private static RecyclableList<int> NewRecyclableList => CreateReversedRecyclableList(_testData);
|
||||
private static readonly IEnumerable<int> _testData = Enumerable.Range(1, _totalObjectCount);
|
||||
private static RecyclableList<int> CreateReversedRecyclableList(IEnumerable<int> source) => new(source);
|
||||
private static RecyclableList<int> CreateReversedRecyclableList(IEnumerable<int> source) => new(source, 1024);
|
||||
|
||||
[Fact]
|
||||
public void AddShouldAddItems()
|
||||
@@ -85,6 +85,53 @@ namespace Recyclable.CollectionsTests
|
||||
_ = list.LongCount.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ContainsShouldFindAllItems()
|
||||
{
|
||||
// Prepare
|
||||
var testData = _testData;
|
||||
using var list = NewRecyclableList;
|
||||
_ = testData.Any().Should().BeTrue("we need items on the list that we can look for");
|
||||
|
||||
// Act
|
||||
foreach (var item in testData)
|
||||
{
|
||||
// Validate
|
||||
_ = list.Contains(item).Should().BeTrue();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ContainsShouldNotFindNonExistingItems()
|
||||
{
|
||||
// Prepare
|
||||
var testData = _testData;
|
||||
using var list = NewRecyclableList;
|
||||
_ = testData.Any().Should().BeTrue("we need items on the list that we can look for");
|
||||
|
||||
// Act
|
||||
foreach (var item in testData)
|
||||
{
|
||||
// Validate
|
||||
_ = list.Contains(-item).Should().BeFalse();
|
||||
}
|
||||
|
||||
// Validate
|
||||
_ = list.Contains(0).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ContainsShouldNotFindAnythingWhenListEmpty()
|
||||
{
|
||||
// Prepare
|
||||
using var list = new RecyclableList<int>();
|
||||
|
||||
// Validate
|
||||
_ = list.Contains(0).Should().BeFalse();
|
||||
_ = list.Contains(1).Should().BeFalse();
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void CopyToShouldCopyAllItems()
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user