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
+686
-260
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.
|
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
|
## 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>`
|
## 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.
|
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
|
## 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.
|
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.
|
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
|
# 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.
|
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
|
# Use
|
||||||
All the classes included in this package are meant to be direct replacements of their corresponding system classes.
|
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>`
|
* `List<T>` ⇨ `RecyclableList<T>`
|
||||||
* `SortedList<T>` ⇨ `RecyclableSortedList<T>`
|
|
||||||
* `Queue<T>` ⇨ `RecyclableQueue<T>`
|
* `Queue<T>` ⇨ `RecyclableQueue<T>`
|
||||||
* `PriorityQueue<T>` ⇨ `RecyclableSortedList<T>`
|
* `PriorityQueue<T>` ⇨ `RecyclableSortedList<T>`
|
||||||
|
* `SortedList<T>` ⇨ `RecyclableSortedList<T>`
|
||||||
* `Stack<T>` ⇨ `RecyclableStack<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.
|
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>`
|
## `RecyclableList<T>`
|
||||||
|
|
||||||
```CSharp
|
```CSharp
|
||||||
@@ -111,3 +124,9 @@ public void RecyclableListExample()
|
|||||||
list.Clear();
|
list.Clear();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
## `RecyclableArrayList<T>`
|
||||||
|
|
||||||
|
## `RecyclableArrayList<T>`
|
||||||
|
|
||||||
|
## `RecyclableArrayList<T>`
|
||||||
|
|
||||||
@@ -21,6 +21,8 @@ namespace Recyclable.Collections.Benchmarks
|
|||||||
|
|
||||||
protected RecyclableArrayList<object>? _testObjectsAsRecyclableArrayList;
|
protected RecyclableArrayList<object>? _testObjectsAsRecyclableArrayList;
|
||||||
protected RecyclableArrayList<object> TestObjectsAsRecyclableArrayList => _testObjectsAsRecyclableArrayList ?? throw new NullReferenceException("Something is wrong and the field is not initialized");
|
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 IEnumerable<object> TestObjectsAsIEnumerable => Enumerable.Range(1, TestObjectCount).Select(static x => new object());
|
||||||
|
|
||||||
protected static void DoNothing<T>(T obj)
|
protected static void DoNothing<T>(T obj)
|
||||||
|
|||||||
@@ -1,16 +1,50 @@
|
|||||||
using BenchmarkDotNet.Running;
|
using BenchmarkDotNet.Configs;
|
||||||
|
using BenchmarkDotNet.Running;
|
||||||
using Recyclable.Collections.Benchmarks;
|
using Recyclable.Collections.Benchmarks;
|
||||||
|
|
||||||
//BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(new[] { "-f*" });
|
//BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(new[] { "-f*" });
|
||||||
|
|
||||||
//BenchmarkRunner.Run<RecyclableCollectionsBenchmarks>();
|
|
||||||
//BenchmarkRunner.Run<RefVsInstanceMemberBenchmarks>();
|
//BenchmarkRunner.Run<RefVsInstanceMemberBenchmarks>();
|
||||||
|
|
||||||
var benchmark = new RecyclableCollectionsBenchmarks();
|
|
||||||
benchmark.Setup();
|
|
||||||
//benchmark.RecyclableArrayList_AddRange_WithCapacity();
|
internal class Program
|
||||||
//benchmark.RecyclableArrayList_Add_WithCapacity();
|
{
|
||||||
benchmark.RecyclableList_Add_WithCapacity();
|
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>();
|
//BenchmarkRunner.Run<DelegateVsComparerBenchmark>();
|
||||||
|
|||||||
@@ -111,5 +111,53 @@ namespace Recyclable.Collections.Benchmarks
|
|||||||
var data = TestObjectsAsList;
|
var data = TestObjectsAsList;
|
||||||
DoNothing(data.Count);
|
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
|
public partial class RecyclableCollectionsBenchmarks : BenchmarkBase
|
||||||
{
|
{
|
||||||
[Benchmark]
|
//[Benchmark]
|
||||||
public void RecyclableArrayList_Create()
|
public void RecyclableArrayList_Create()
|
||||||
{
|
{
|
||||||
using var list = new RecyclableArrayList<object>();
|
using var list = new RecyclableArrayList<object>();
|
||||||
DoNothing(list);
|
DoNothing(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark]
|
//[Benchmark]
|
||||||
public void RecyclableArrayList_Create_WithCapacity()
|
public void RecyclableArrayList_Create_WithCapacity()
|
||||||
{
|
{
|
||||||
using var list = new RecyclableArrayList<object>(TestObjectCount);
|
using var list = new RecyclableArrayList<object>(TestObjectCount);
|
||||||
DoNothing(list);
|
DoNothing(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark]
|
//[Benchmark]
|
||||||
public void RecyclableArrayList_Add()
|
public void RecyclableArrayList_Add()
|
||||||
{
|
{
|
||||||
var data = TestObjects;
|
var data = TestObjects;
|
||||||
@@ -30,7 +30,7 @@ namespace Recyclable.Collections.Benchmarks
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark]
|
//[Benchmark]
|
||||||
public void RecyclableArrayList_Add_WithCapacity()
|
public void RecyclableArrayList_Add_WithCapacity()
|
||||||
{
|
{
|
||||||
var data = TestObjects;
|
var data = TestObjects;
|
||||||
|
|||||||
+54
-6
@@ -4,21 +4,21 @@ namespace Recyclable.Collections.Benchmarks
|
|||||||
{
|
{
|
||||||
public partial class RecyclableCollectionsBenchmarks : BenchmarkBase
|
public partial class RecyclableCollectionsBenchmarks : BenchmarkBase
|
||||||
{
|
{
|
||||||
[Benchmark]
|
//[Benchmark]
|
||||||
public void RecyclableList_Create()
|
public void RecyclableList_Create()
|
||||||
{
|
{
|
||||||
using var list = new RecyclableList<object>();
|
using var list = new RecyclableList<object>();
|
||||||
DoNothing(list);
|
DoNothing(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark]
|
//[Benchmark]
|
||||||
public void RecyclableList_Create_WithCapacity()
|
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);
|
DoNothing(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark]
|
//[Benchmark]
|
||||||
public void RecyclableList_Add()
|
public void RecyclableList_Add()
|
||||||
{
|
{
|
||||||
var data = TestObjects;
|
var data = TestObjects;
|
||||||
@@ -30,12 +30,12 @@ namespace Recyclable.Collections.Benchmarks
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Benchmark]
|
//[Benchmark]
|
||||||
public void RecyclableList_Add_WithCapacity()
|
public void RecyclableList_Add_WithCapacity()
|
||||||
{
|
{
|
||||||
var data = TestObjects;
|
var data = TestObjects;
|
||||||
var dataCount = TestObjectCount;
|
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++)
|
for (var i = 0; i < dataCount; i++)
|
||||||
{
|
{
|
||||||
list.Add(data[i]);
|
list.Add(data[i]);
|
||||||
@@ -77,5 +77,53 @@ namespace Recyclable.Collections.Benchmarks
|
|||||||
var data = _testRecyclableList;
|
var data = _testRecyclableList;
|
||||||
DoNothing(data.LongCount);
|
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 BenchmarkDotNet.Attributes;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Recyclable.Collections.Benchmarks
|
namespace Recyclable.Collections.Benchmarks
|
||||||
{
|
{
|
||||||
@@ -18,11 +13,12 @@ namespace Recyclable.Collections.Benchmarks
|
|||||||
{
|
{
|
||||||
Console.WriteLine("******* GLOBAL SETUP RAISED *******");
|
Console.WriteLine("******* GLOBAL SETUP RAISED *******");
|
||||||
_testObjects = Enumerable.Range(1, TestObjectCount).Select(x => new object()).ToArray();
|
_testObjects = Enumerable.Range(1, TestObjectCount).Select(x => new object()).ToArray();
|
||||||
_testRecyclableArrayList = new(TestObjects, TestObjectCount);
|
_testRecyclableArrayList = new(TestObjects, initialCapacity: TestObjectCount);
|
||||||
_testRecyclableList = new(TestObjects, TestObjectCount);
|
_testRecyclableList = new(TestObjects, expectedItemsCount: TestObjectCount);
|
||||||
_testArray = TestObjects.ToArray();
|
_testArray = TestObjects.ToArray();
|
||||||
_testObjectsAsList = TestObjects.ToList();
|
_testObjectsAsList = TestObjects.ToList();
|
||||||
_testObjectsAsRecyclableArrayList = TestObjects.ToRecyclableArrayList();
|
_testObjectsAsRecyclableArrayList = TestObjects.ToRecyclableArrayList();
|
||||||
|
_testObjectsAsRecyclableList = TestObjects.ToRecyclableList();
|
||||||
}
|
}
|
||||||
|
|
||||||
[GlobalCleanup]
|
[GlobalCleanup]
|
||||||
@@ -33,6 +29,7 @@ namespace Recyclable.Collections.Benchmarks
|
|||||||
_testRecyclableList?.Dispose();
|
_testRecyclableList?.Dispose();
|
||||||
TestObjectsAsRecyclableArrayList?.Dispose();
|
TestObjectsAsRecyclableArrayList?.Dispose();
|
||||||
_testObjectsAsRecyclableArrayList = default;
|
_testObjectsAsRecyclableArrayList = default;
|
||||||
|
_testObjectsAsRecyclableList = default;
|
||||||
_testObjectsAsList = default;
|
_testObjectsAsList = default;
|
||||||
_testObjects = default;
|
_testObjects = default;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,27 +21,22 @@ namespace Recyclable.Collections
|
|||||||
{
|
{
|
||||||
ArrayPool<T> arrayPool = _arrayPool;
|
ArrayPool<T> arrayPool = _arrayPool;
|
||||||
var sourceLength = source?.Length ?? 0;
|
var sourceLength = source?.Length ?? 0;
|
||||||
T[] newMemoryBlock = newSize.RentArrayFromPool(arrayPool);
|
T[] newMemoryBlock = newSize >= RecyclableDefaults.MinPooledArrayLength
|
||||||
switch (newSize >= sourceLength)
|
? arrayPool.Rent(newSize)
|
||||||
|
: new T[newSize];
|
||||||
|
|
||||||
|
if (sourceLength > 0)
|
||||||
{
|
{
|
||||||
case true:
|
var sourceSpan = new Memory<T>(source);
|
||||||
if (sourceLength > 0)
|
var newArraySpan = new Memory<T>(newMemoryBlock);
|
||||||
{
|
sourceSpan.CopyTo(newArraySpan);
|
||||||
Memory<T> sourceMemory = new(source);
|
if (sourceLength >= RecyclableDefaults.MinPooledArrayLength)
|
||||||
Memory<T> newArrayMemory = new(newMemoryBlock);
|
{
|
||||||
sourceMemory.CopyTo(newArrayMemory);
|
arrayPool.Return(source!);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return newMemoryBlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected int EnsureCapacity(in int requestedCapacity)
|
protected int EnsureCapacity(in int requestedCapacity)
|
||||||
@@ -153,7 +148,7 @@ namespace Recyclable.Collections
|
|||||||
|
|
||||||
public bool IsReadOnly { get; } = false;
|
public bool IsReadOnly { get; } = false;
|
||||||
|
|
||||||
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Add(T item)
|
public void Add(T item)
|
||||||
{
|
{
|
||||||
int requiredCapacity = _count + 1;
|
int requiredCapacity = _count + 1;
|
||||||
@@ -247,41 +242,41 @@ namespace Recyclable.Collections
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int capacity = _capacity;
|
int targetItemIdx = _count;
|
||||||
int targetIndex = _count;
|
|
||||||
Span<T> memorySpan;
|
Span<T> memorySpan;
|
||||||
if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity))
|
if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity))
|
||||||
{
|
{
|
||||||
_ = EnsureCapacity(targetIndex + requiredAdditionalCapacity);
|
_ = EnsureCapacity(targetItemIdx + requiredAdditionalCapacity);
|
||||||
memorySpan = new(_memoryBlock);
|
memorySpan = new(_memoryBlock);
|
||||||
foreach (var item in source)
|
foreach (var item in source)
|
||||||
{
|
{
|
||||||
memorySpan[targetIndex++] = item;
|
memorySpan[targetItemIdx++] = item;
|
||||||
}
|
}
|
||||||
|
|
||||||
_count = targetIndex;
|
_count = targetItemIdx;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int i;
|
int i;
|
||||||
using var enumerator = source.GetEnumerator();
|
using var enumerator = source.GetEnumerator();
|
||||||
|
|
||||||
|
int capacity = _capacity;
|
||||||
memorySpan = new(_memoryBlock);
|
memorySpan = new(_memoryBlock);
|
||||||
if (enumerator.MoveNext())
|
if (enumerator.MoveNext())
|
||||||
{
|
{
|
||||||
int available = capacity - targetIndex;
|
int available = capacity - targetItemIdx;
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (targetIndex + growByCount > capacity)
|
if (targetItemIdx + growByCount > capacity)
|
||||||
{
|
{
|
||||||
capacity = EnsureCapacity(capacity + growByCount);
|
capacity = EnsureCapacity(capacity + growByCount);
|
||||||
memorySpan = new(_memoryBlock);
|
memorySpan = new(_memoryBlock);
|
||||||
available = capacity - targetIndex;
|
available = capacity - targetItemIdx;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < available; i++)
|
for (i = 0; i < available; i++)
|
||||||
{
|
{
|
||||||
memorySpan[targetIndex++] = enumerator.Current;
|
memorySpan[targetItemIdx++] = enumerator.Current;
|
||||||
if (!enumerator.MoveNext())
|
if (!enumerator.MoveNext())
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
@@ -295,7 +290,7 @@ namespace Recyclable.Collections
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_count = targetIndex;
|
_count = targetItemIdx;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Clear()
|
public void Clear()
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
{
|
{
|
||||||
public class RecyclableDefaults
|
public class RecyclableDefaults
|
||||||
{
|
{
|
||||||
public const int BlockSize = 10_240;
|
public const int BlockSize = 16_384;
|
||||||
public const int Capacity = 1;
|
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.Collections;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
@@ -7,11 +6,18 @@ namespace Recyclable.Collections
|
|||||||
{
|
{
|
||||||
public class RecyclableList<T> : IDisposable, IList<T>
|
public class RecyclableList<T> : IDisposable, IList<T>
|
||||||
{
|
{
|
||||||
private static readonly ArrayPool<T[]> _arrayPool = ArrayPool<T[]>.Create();
|
private const int ItemNotFoundIndex = -1;
|
||||||
private static readonly ArrayPool<T> _blockArrayPool = ArrayPool<T>.Create();
|
private static readonly ArrayPool<T[]> _defaultMemoryBlocksPool = ArrayPool<T[]>.Create();
|
||||||
private static readonly T[][] _emptyBlockArray = new T[0][];
|
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 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;
|
protected T[][] _memoryBlocks;
|
||||||
|
|
||||||
private long _capacity;
|
private long _capacity;
|
||||||
@@ -21,7 +27,7 @@ namespace Recyclable.Collections
|
|||||||
protected set => _capacity = value;
|
protected set => _capacity = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Count => (int)LongCount;
|
public int Count => (int)_longCount;
|
||||||
|
|
||||||
protected long _longCount;
|
protected long _longCount;
|
||||||
public long LongCount
|
public long LongCount
|
||||||
@@ -30,13 +36,12 @@ namespace Recyclable.Collections
|
|||||||
set => _longCount = value;
|
set => _longCount = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsReadOnly { get; } = false;
|
public bool IsReadOnly { get; }
|
||||||
public int BlockCount => _memoryBlocks.Length;
|
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 = list._longCount - 1;
|
||||||
long oldCountMinus1 = oldCount - 1;
|
|
||||||
if (index != oldCountMinus1)
|
if (index != oldCountMinus1)
|
||||||
{
|
{
|
||||||
ThrowArgumentOutOfRangeException();
|
ThrowArgumentOutOfRangeException();
|
||||||
@@ -44,10 +49,25 @@ namespace Recyclable.Collections
|
|||||||
|
|
||||||
list._longCount--;
|
list._longCount--;
|
||||||
int blockSize = list._blockSize;
|
int blockSize = list._blockSize;
|
||||||
|
if (list._nextItemIndex > 0)
|
||||||
|
{
|
||||||
|
list._nextItemIndex--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
list._lastBlockIndex--;
|
||||||
|
list._nextItemIndex = blockSize;
|
||||||
|
}
|
||||||
|
|
||||||
if ((list._capacity * blockSize) - oldCountMinus1 == blockSize)
|
if ((list._capacity * blockSize) - oldCountMinus1 == blockSize)
|
||||||
{
|
{
|
||||||
T[][] memoryBlocks = list._memoryBlocks;
|
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)]
|
[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;
|
const int sourceBlockCount = 0, requiredBlockCount = 1, uninitializedBlocksCount = 2, i = 3;
|
||||||
ArrayPool<T> blockArrayPool = _blockArrayPool;
|
|
||||||
var sourceBlockCount = source?.Length ?? 0;
|
Span<int> localInts = stackalloc int[4]
|
||||||
int requiredBlockCount = (int)(newSize / blockSize) + (newSize % blockSize > 0 ? 1 : 0);
|
|
||||||
if (requiredBlockCount == sourceBlockCount)
|
|
||||||
{
|
{
|
||||||
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;
|
localInts[uninitializedBlocksCount] = newMemoryBlocksSpan.Length;
|
||||||
T[][] newMemoryBlocks = requiredBlockCount.RentArrayFromPool(arrayPool);
|
if (localInts[uninitializedBlocksCount] == localInts[requiredBlockCount])
|
||||||
switch (requiredBlockCount >= sourceBlockCount)
|
|
||||||
{
|
{
|
||||||
case true:
|
if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength)
|
||||||
if (sourceBlockCount > 0)
|
{
|
||||||
{
|
newMemoryBlocksSpan[0] = blockArrayPool.Rent(minBlockSize);
|
||||||
Memory<T[]> sourceMemory = new(source);
|
minBlockSize = newMemoryBlocksSpan[0].Length;
|
||||||
Memory<T[]> newArrayMemory = new(newMemoryBlocks);
|
}
|
||||||
sourceMemory.CopyTo(newArrayMemory);
|
else
|
||||||
source!.ReturnToPool(arrayPool);
|
{
|
||||||
newArraySpan = new Span<T[]>(newMemoryBlocks)[sourceBlockCount..];
|
newMemoryBlocksSpan[0] = new T[minBlockSize];
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
newArraySpan = new Span<T[]>(newMemoryBlocks);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < newArraySpan.Length; i++)
|
newMemoryBlocksSpan = newMemoryBlocksSpan[1..];
|
||||||
{
|
localInts[uninitializedBlocksCount]--;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
protected long EnsureCapacity(in long requestedCapacity)
|
||||||
{
|
{
|
||||||
long oldCapacity = _capacity;
|
T[][] memory = _memoryBlocks;
|
||||||
ref T[][] memory = ref _memoryBlocks;
|
|
||||||
|
|
||||||
long newCapacity;
|
long newCapacity;
|
||||||
switch (oldCapacity > 0)
|
|
||||||
|
if (_capacity > 0)
|
||||||
{
|
{
|
||||||
case true:
|
newCapacity = _capacity;
|
||||||
newCapacity = oldCapacity;
|
while (newCapacity < requestedCapacity)
|
||||||
while (newCapacity < requestedCapacity)
|
{
|
||||||
{
|
newCapacity *= 2;
|
||||||
newCapacity *= 2;
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
break;
|
{
|
||||||
|
newCapacity = requestedCapacity;
|
||||||
case false:
|
|
||||||
newCapacity = requestedCapacity;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int blockSize = _blockSize;
|
memory = SetNewLength(memory, _blockSize, newCapacity, _memoryBlocksPool, _blockArrayPool);
|
||||||
memory = SetNewLength(memory, blockSize, newCapacity);
|
if (newCapacity > 0 && _capacity == 0)
|
||||||
newCapacity = memory.Length * blockSize;
|
{
|
||||||
|
_blockSize = memory[0].Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
newCapacity = memory.Length * _blockSize;
|
||||||
_capacity = newCapacity;
|
_capacity = newCapacity;
|
||||||
|
_memoryBlocks = memory;
|
||||||
return newCapacity;
|
return newCapacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning disable CS8618 // _memoryBlocks will be initialized when the 1st item is added
|
public RecyclableList(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool<T[]>? memoryBlocksPool = default, ArrayPool<T>? blockArrayPool = default)
|
||||||
public RecyclableList(int blockSize = RecyclableDefaults.BlockSize, long? initialCapacity = default)
|
|
||||||
#pragma warning restore CS8618
|
|
||||||
{
|
{
|
||||||
_blockSize = blockSize;
|
memoryBlocksPool ??= _defaultMemoryBlocksPool;
|
||||||
if (initialCapacity > 0)
|
blockArrayPool ??= _defaultBlockArrayPool;
|
||||||
|
_memoryBlocksPool = memoryBlocksPool;
|
||||||
|
_blockArrayPool = blockArrayPool;
|
||||||
|
|
||||||
|
if (expectedItemsCount > 0)
|
||||||
{
|
{
|
||||||
_memoryBlocks = SetNewLength(_memoryBlocks, blockSize, initialCapacity.Value);
|
_memoryBlocks = SetNewLength(_memoryBlocks, minBlockSize, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool);
|
||||||
_capacity = _memoryBlocks.Length * blockSize;
|
if (_memoryBlocks.Length > 0)
|
||||||
|
{
|
||||||
|
minBlockSize = _memoryBlocks[0].Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
_blockSize = minBlockSize;
|
||||||
|
_capacity = _memoryBlocks.Length * minBlockSize;
|
||||||
}
|
}
|
||||||
else
|
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 minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool<T[]>? memoryBlocksPool = default, ArrayPool<T>? blockArrayPool = default)
|
||||||
public RecyclableList(IEnumerable<T> source, int blockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default)
|
|
||||||
#pragma warning restore CS8618
|
|
||||||
{
|
{
|
||||||
_blockSize = blockSize;
|
memoryBlocksPool ??= _defaultMemoryBlocksPool;
|
||||||
|
blockArrayPool ??= _defaultBlockArrayPool;
|
||||||
|
_memoryBlocksPool = memoryBlocksPool;
|
||||||
|
_blockArrayPool = blockArrayPool;
|
||||||
if (expectedItemsCount > 0)
|
if (expectedItemsCount > 0)
|
||||||
{
|
{
|
||||||
_memoryBlocks = SetNewLength(_memoryBlocks, blockSize, expectedItemsCount.Value);
|
_memoryBlocks = SetNewLength(_memoryBlocks, minBlockSize, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool);
|
||||||
_capacity = _memoryBlocks.Length * blockSize;
|
if (_memoryBlocks.Length > 0)
|
||||||
|
{
|
||||||
|
minBlockSize = _memoryBlocks[0].Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
_blockSize = minBlockSize;
|
||||||
|
_capacity = _memoryBlocks.Length * minBlockSize;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_memoryBlocks = _emptyBlockArray;
|
_blockSize = minBlockSize;
|
||||||
|
_memoryBlocks = _emptyMemoryBlocksArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
AddRange(this, source);
|
AddRange(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
public T this[long index]
|
public T this[long index]
|
||||||
{
|
{
|
||||||
get => _memoryBlocks[(int)(index / _blockSize)][index % _blockSize];
|
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]
|
public T this[int index]
|
||||||
@@ -183,17 +234,23 @@ namespace Recyclable.Collections
|
|||||||
set => new Span<T>(_memoryBlocks[index / _blockSize])[index % _blockSize] = value;
|
set => new Span<T>(_memoryBlocks[index / _blockSize])[index % _blockSize] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public T[][] MemoryBlocks { get => _memoryBlocks; }
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Add(T item)
|
public void Add(T item)
|
||||||
{
|
{
|
||||||
int blockSize = _blockSize;
|
if (_capacity < _longCount + 1)
|
||||||
long oldCount = _longCount;
|
|
||||||
long requiredCapacity = oldCount + 1;
|
|
||||||
if (_capacity < requiredCapacity)
|
|
||||||
{
|
{
|
||||||
_ = EnsureCapacity(requiredCapacity);
|
_ = EnsureCapacity(_longCount + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
_memoryBlocks[_lastBlockIndex][_nextItemIndex++] = item;
|
||||||
|
if (_nextItemIndex == _blockSize)
|
||||||
|
{
|
||||||
|
_lastBlockIndex++;
|
||||||
|
_nextItemIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
_memoryBlocks[(int)(oldCount / blockSize)][oldCount % blockSize] = item;
|
|
||||||
_longCount++;
|
_longCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,20 +267,28 @@ namespace Recyclable.Collections
|
|||||||
int blockSize = _blockSize;
|
int blockSize = _blockSize;
|
||||||
int targetItemIdx = (int)(oldLongCount % blockSize);
|
int targetItemIdx = (int)(oldLongCount % blockSize);
|
||||||
int targetBlockIdx = (int)(oldLongCount / blockSize) + (targetItemIdx > 0 ? 1 : 0);
|
int targetBlockIdx = (int)(oldLongCount / blockSize) + (targetItemIdx > 0 ? 1 : 0);
|
||||||
var memoryBlockSpan = new Span<T[]>(_memoryBlocks);
|
Span<T[]> memoryBlocksSpan = new(_memoryBlocks);
|
||||||
var targetBlockSpan = new Span<T>(memoryBlockSpan[targetBlockIdx]);
|
int memoryBlocksCount = memoryBlocksSpan.Length;
|
||||||
for (long i = 0; i < sourceItemsCount; i++)
|
Span<T> targetBlockSpan = new(memoryBlocksSpan[targetBlockIdx]);
|
||||||
|
for (var i = 0L; i < sourceItemsCount; i++)
|
||||||
{
|
{
|
||||||
targetBlockSpan[targetItemIdx++] = items[i];
|
targetBlockSpan[targetItemIdx++] = items[i];
|
||||||
if (targetItemIdx == blockSize)
|
if (targetItemIdx == blockSize)
|
||||||
{
|
{
|
||||||
targetBlockIdx++;
|
targetBlockIdx++;
|
||||||
targetBlockSpan = new Span<T>(memoryBlockSpan[targetBlockIdx]);
|
if (targetBlockIdx == memoryBlocksCount)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
targetBlockSpan = new Span<T>(memoryBlocksSpan[targetBlockIdx]);
|
||||||
targetItemIdx = 0;
|
targetItemIdx = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_longCount = targetCapacity;
|
_longCount = targetCapacity;
|
||||||
|
_lastBlockIndex = targetBlockIdx - 1;
|
||||||
|
_nextItemIndex = targetItemIdx;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddRange(in List<T> items)
|
public void AddRange(in List<T> items)
|
||||||
@@ -239,20 +304,28 @@ namespace Recyclable.Collections
|
|||||||
int blockSize = _blockSize;
|
int blockSize = _blockSize;
|
||||||
int targetItemIdx = (int)(oldLongCount % blockSize);
|
int targetItemIdx = (int)(oldLongCount % blockSize);
|
||||||
int targetBlockIdx = (int)(oldLongCount / blockSize) + (targetItemIdx > 0 ? 1 : 0);
|
int targetBlockIdx = (int)(oldLongCount / blockSize) + (targetItemIdx > 0 ? 1 : 0);
|
||||||
var memoryBlockSpan = new Span<T[]>(_memoryBlocks);
|
Span<T[]> memoryBlocksSpan = new(_memoryBlocks);
|
||||||
var targetBlockSpan = new Span<T>(memoryBlockSpan[targetBlockIdx]);
|
var memoryBlocksCount = memoryBlocksSpan.Length;
|
||||||
|
Span<T> targetBlockSpan = new(memoryBlocksSpan[targetBlockIdx]);
|
||||||
for (int i = 0; i < sourceItemsCount; i++)
|
for (int i = 0; i < sourceItemsCount; i++)
|
||||||
{
|
{
|
||||||
targetBlockSpan[targetItemIdx++] = items[i];
|
targetBlockSpan[targetItemIdx++] = items[i];
|
||||||
if (targetItemIdx == blockSize)
|
if (targetItemIdx == blockSize)
|
||||||
{
|
{
|
||||||
targetBlockIdx++;
|
targetBlockIdx++;
|
||||||
targetBlockSpan = new Span<T>(memoryBlockSpan[targetBlockIdx]);
|
if (targetBlockIdx == memoryBlocksCount)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
targetBlockSpan = new Span<T>(memoryBlocksSpan[targetBlockIdx]);
|
||||||
targetItemIdx = 0;
|
targetItemIdx = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_longCount = targetCapacity;
|
_longCount = targetCapacity;
|
||||||
|
_lastBlockIndex = targetBlockIdx - 1;
|
||||||
|
_nextItemIndex = targetItemIdx;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddRange(in IList<T> items)
|
public void AddRange(in IList<T> items)
|
||||||
@@ -268,114 +341,292 @@ namespace Recyclable.Collections
|
|||||||
int blockSize = _blockSize;
|
int blockSize = _blockSize;
|
||||||
int targetItemIdx = (int)(oldLongCount % blockSize);
|
int targetItemIdx = (int)(oldLongCount % blockSize);
|
||||||
int targetBlockIdx = (int)(oldLongCount / blockSize) + (targetItemIdx > 0 ? 1 : 0);
|
int targetBlockIdx = (int)(oldLongCount / blockSize) + (targetItemIdx > 0 ? 1 : 0);
|
||||||
var memoryBlockSpan = new Span<T[]>(_memoryBlocks);
|
Span<T[]> memoryBlocksSpan = new(_memoryBlocks);
|
||||||
var targetBlockSpan = new Span<T>(memoryBlockSpan[targetBlockIdx]);
|
var memoryBlocksCount = memoryBlocksSpan.Length;
|
||||||
|
Span<T> targetBlockSpan = new(memoryBlocksSpan[targetBlockIdx]);
|
||||||
for (int i = 0; i < sourceItemsCount; i++)
|
for (int i = 0; i < sourceItemsCount; i++)
|
||||||
{
|
{
|
||||||
targetBlockSpan[targetItemIdx++] = items[i];
|
targetBlockSpan[targetItemIdx++] = items[i];
|
||||||
if (targetItemIdx == blockSize)
|
if (targetItemIdx == blockSize)
|
||||||
{
|
{
|
||||||
targetBlockIdx++;
|
targetBlockIdx++;
|
||||||
targetBlockSpan = new Span<T>(memoryBlockSpan[targetBlockIdx]);
|
if (targetBlockIdx == memoryBlocksCount)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
targetBlockSpan = new Span<T>(memoryBlocksSpan[targetBlockIdx]);
|
||||||
targetItemIdx = 0;
|
targetItemIdx = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_longCount = targetCapacity;
|
_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()
|
public void Clear()
|
||||||
{
|
{
|
||||||
try
|
if (_blockSize >= RecyclableDefaults.MinPooledArrayLength)
|
||||||
{
|
{
|
||||||
// Remove in reversed order for performance savings
|
Span<T[]> memoryBlocksSpan = new(_memoryBlocks);
|
||||||
var toRemoveIdx = _memoryBlocks.Length - 1;
|
int memoryBlocksCount = memoryBlocksSpan.Length;
|
||||||
while (toRemoveIdx >= 0)
|
ArrayPool<T> blockArrayPool = _blockArrayPool;
|
||||||
|
for (int toRemoveIdx = 0; toRemoveIdx < memoryBlocksCount; toRemoveIdx++)
|
||||||
{
|
{
|
||||||
try
|
blockArrayPool.Return(memoryBlocksSpan[toRemoveIdx]);
|
||||||
{
|
|
||||||
ReleaseBlock(this, _memoryBlocks, _blockSize, toRemoveIdx);
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
// We want to try returning as many arrays, as possible, before
|
|
||||||
// the list is cleared.
|
|
||||||
}
|
|
||||||
|
|
||||||
toRemoveIdx--;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
_capacity = 0;
|
||||||
_longCount = 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 IEnumerator<T> GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator();
|
||||||
|
|
||||||
public int IndexOf(T item) => (int)_memoryBlocks.LongIndexOf(_longCount, item, _equalityComparer);
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Insert(int index, T item) => throw new NotSupportedException();
|
public int IndexOf(T item)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
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)
|
public void RemoveAt(int index) => RemoveAt(this, index, _blockArrayPool);
|
||||||
//{
|
public void RemoveAt(long index) => RemoveAt(this, index, _blockArrayPool);
|
||||||
// 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;
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator();
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (_capacity > 0)
|
if (_capacity > 0)
|
||||||
{
|
{
|
||||||
Clear();
|
Clear();
|
||||||
_capacity = 0;
|
if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||||
_memoryBlocks.ReturnToPool(_arrayPool);
|
{
|
||||||
|
_memoryBlocksPool.Return(_memoryBlocks);
|
||||||
|
}
|
||||||
|
|
||||||
GC.SuppressFinalize(this);
|
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();
|
Memory<T> sourceBlockMemory;
|
||||||
arrayMemory = arrayMemory[destinationArrayIndex..];
|
Memory<T> destinationArrayMemory;
|
||||||
int startingArrayIndex = (int)(startingIndex / blockSize);
|
int startingBlockIndex = (int)(startingIndex / blockSize);
|
||||||
startingIndex %= blockSize;
|
int lastBlockIndex = (int)((itemsCount / blockSize) + (itemsCount - (itemsCount / blockSize) > 0 ? 1 : 0)) - 1;
|
||||||
if (arrays.LongLength > 0)
|
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();
|
sourceBlockMemory = new(sourceMemoryBlocksSpan[memoryBlockIndex], 0, blockSize);
|
||||||
var maxToCopy = (int)Math.Min(blockSize - startingIndex, arrayMemory.Length);
|
sourceBlockMemory.CopyTo(destinationArrayMemory);
|
||||||
if (arrays.LongLength == 1)
|
destinationArrayMemory = destinationArrayMemory[blockSize..];
|
||||||
{
|
|
||||||
maxToCopy = (int)Math.Min(maxToCopy, lastBlockSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceMemory = sourceMemory[(int)startingIndex..maxToCopy];
|
|
||||||
sourceMemory.CopyTo(arrayMemory);
|
|
||||||
arrayMemory = arrayMemory[maxToCopy..];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var arrayIdx = startingArrayIndex + 1; arrayIdx < arrays.Length - 1 && arrayMemory.Length > 0; arrayIdx++)
|
if (itemsCount % blockSize > 0)
|
||||||
{
|
{
|
||||||
ReadOnlySpan<T> sourceMemory = arrays[arrayIdx].AsSpan();
|
sourceBlockMemory = new(sourceMemoryBlocksSpan[^1], 0, (int)(itemsCount % blockSize));
|
||||||
var maxToCopy = Math.Min(blockSize, arrayMemory.Length);
|
sourceBlockMemory.CopyTo(destinationArrayMemory);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace Recyclable.CollectionsTests
|
|||||||
private const int _totalObjectCount = 5_000;
|
private const int _totalObjectCount = 5_000;
|
||||||
private static RecyclableList<int> NewRecyclableList => CreateReversedRecyclableList(_testData);
|
private static RecyclableList<int> NewRecyclableList => CreateReversedRecyclableList(_testData);
|
||||||
private static readonly IEnumerable<int> _testData = Enumerable.Range(1, _totalObjectCount);
|
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]
|
[Fact]
|
||||||
public void AddShouldAddItems()
|
public void AddShouldAddItems()
|
||||||
@@ -85,6 +85,53 @@ namespace Recyclable.CollectionsTests
|
|||||||
_ = list.LongCount.Should().Be(0);
|
_ = 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]
|
[Fact]
|
||||||
public void CopyToShouldCopyAllItems()
|
public void CopyToShouldCopyAllItems()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in new issue
Block a user