Merge branch 'main' of https://github.com/mlemanczyk/Recyclable.Collections
This commit is contained in:
commit
9e07e0181c
6 files changed
+289
-7
No files matched your search
@@ -75,10 +75,9 @@
|
||||
1. ✅ `RecyclableSortedList<T>`
|
||||
1. ✅ `RecyclableStack<T>`
|
||||
1. ✅ `RecyclableUnorderedList<T>`
|
||||
1. 👉 Add support for `ReadOnlySpan<T>`
|
||||
1. 🅿️ Implement list versioning to allow data change identification
|
||||
1. ✅ Add support for `ReadOnlySpan<T>`
|
||||
1. ✅ Release 0.0.3-alpha
|
||||
1. 🅿️ Implement `List<T>` interfaces
|
||||
1. 👉 Implement `List<T>` interfaces
|
||||
1. 🅿️ `ICollection<T>`
|
||||
1. 🅿️ `IEnumerable<T>`
|
||||
1. 🅿️ `IEnumerable`
|
||||
@@ -87,6 +86,7 @@
|
||||
1. 🅿️ `IReadOnlyList<T>`
|
||||
1. 🅿️ `ICollection`
|
||||
1. 🅿️ `IList`
|
||||
1. 🅿️ Implement list versioning to allow data change identification
|
||||
1. 🅿️ Make sure that `NeedsClearing` is used & items are cleared in
|
||||
1. 🅿️ `Clear`
|
||||
1. 🅿️ `Dispose`
|
||||
@@ -111,6 +111,9 @@
|
||||
1. 🅿️ Make type castings `checked`
|
||||
1. 🅿️ Port `RecyclableLongList<T>` optimizations to `RecyclableList<T>`
|
||||
1. 🅿️ Release 0.0.3
|
||||
1. 🅿️ Implement `ILongList<T>` interface
|
||||
1. 🅿️ `RecyclableList<T>`
|
||||
1. 🅿️ `RecyclableLongList<T>`
|
||||
1. 🅿️ Implement `RecyclableQeueue<T>`
|
||||
1. 🅿️ Port `RecyclableLongList<T>` optimizations to `RecyclableQueue<T>`
|
||||
1. 🅿️ Release 0.0.4
|
||||
@@ -126,9 +129,11 @@
|
||||
1. 🅿️ Optimize `OneSizeArrayPool`
|
||||
1. 🅿️ Review locks
|
||||
1. 🅿️ Measure multi-threading performance
|
||||
1. 🅿️ Implement memory bucket disposal in high RAM pressure scenario
|
||||
1. 🅿️ Review `RecyclableArrayPool`
|
||||
1. 🅿️ Review locks
|
||||
1. 🅿️ Measure multi-threading performance
|
||||
1. 🅿️ Implement array disposal in high RAM pressure scenario
|
||||
1. 🅿️ Optimize `MemoryBucket<T>`
|
||||
1. 🅿️ Convert to `struct`, if possible
|
||||
1. 🅿️ Find out if there are better replacements
|
||||
@@ -137,8 +142,11 @@
|
||||
1. 🅿️ `IndexOfSynchronizationContext`
|
||||
1. 🅿️ `IndexOfSynchronizationContextPool`
|
||||
1. 🅿️ `ManualResetEventSlimmer`
|
||||
1. 🅿️ Multi-threading benchmarks
|
||||
1. 🅿️ `ManualResetEventSlimmerPool`
|
||||
1. 🅿️ Multi-threading benchmarks
|
||||
1. 🅿️ `SpinLockSlimmer`
|
||||
1. 🅿️ Multi-threading benchmarks
|
||||
1. 🅿️ Release 0.0.9-beta
|
||||
1. 🅿️ Cleanup
|
||||
1. 🅿️ Replace `LastBlockWithData` property with `_lastBlockWithData` field
|
||||
@@ -149,7 +157,18 @@
|
||||
1. 🅿️ `ListExtensions`
|
||||
1. 🅿️ `MathUtils`
|
||||
1. 🅿️ `SystemRandomNumberGenerator`
|
||||
1. 🅿️ Review and remove warnings & hints
|
||||
1. 🅿️ Warnings
|
||||
1. 🅿️ Hints
|
||||
1. Documentation
|
||||
1. 🅿️ Document differences in behavior
|
||||
1. 🅿️ Document other specifics
|
||||
1. 🅿️ Release 1.0.0
|
||||
1. 🅿️ Optimize
|
||||
1. 🅿️ `RecyclableLongList<T>.Resize`
|
||||
1. 🅿️ Check if we can benefit from Sse2 in `.IndexOf`/`.Contains` methods as given in [MS blog](https://devblogs.microsoft.com/dotnet/hardware-intrinsics-in-net-core/).
|
||||
1. 🅿️ Add support for `ICollection<T>` interface in `.AddRange` & `constructor`
|
||||
1. 🅿️ Release 2.0.0
|
||||
|
||||
# Characteristics of the classes
|
||||
|
||||
@@ -174,7 +193,7 @@ This is the direct equivalent of `List<T>` class, except that the arrays are tak
|
||||
* All classes provide large storage capabilities, in practice limited only by the memory available in your system.
|
||||
* All data is stored in blocks, with the provided `int blockSize` or the default, currently being `10,240` items in each block. It's recommended to use smaller numbers, if you foresee storing low no. of items on the list.
|
||||
* There is no memory copying to increase the capacity of the recyclable classes, unless the no. of items exceeds the no. of allocated blocks. In that case, a new array of blocks is created to accommodate . If more capacity is needed, a new memory block is allocate & added to the internal list of blocks. This is the only list that may re-allocate memory block & copy their content to grow. That shouldn't be an issue considering limited no. of blocks stored by each class in practical scenarios. Currently I'm not planning any works around that. Shall there be a need to eliminate it, you're welcomed to file a PR with the proposed changes.
|
||||
* ⚠️ `IndexOf` will automatically switch to parallel search mode. Collections smaller than (currently) 850_000 items are guaranteed to find the 1st occurrence of the `item` parameter. Collections bigger than (currently) 850_000 items will be scanned using parallelization. If either case it'll return the index of the first matching item. This note also applies to `Contains`, because it internally uses `IndexOf`. The value of 850_000 may become configurable in the future, unless it proves to result in much worse performance.
|
||||
* ⚠️ `IndexOf` will automatically switch to parallel search mode. Collections bigger than (currently) 850_000 items will be scanned using parallelization. In either case it'll return the index of the first matching item. This note also applies to `Contains`, because it internally uses `IndexOf`. The value of 850_000 may become configurable in the future, unless it proves to result in much worse performance.
|
||||
|
||||
The parallelization utilizes default `Task` scheduling, resulting in non-deterministic order in which search tasks are executed. In most cases it resulted in significant performance improvements. Below you can find the benchmarks for various item counts.
|
||||
|
||||
|
||||
@@ -68,6 +68,6 @@ namespace Recyclable.Collections.Benchmarks
|
||||
|
||||
protected override Action? GetTestMethod(RecyclableCollectionsBenchmarkSource benchmarkType)
|
||||
=> (Action?) GetType().GetMethod($"{benchmarkType}_{TestCase}", BindingFlags.Instance | BindingFlags.Public)?.CreateDelegate(typeof(Action), this)
|
||||
?? throw CreateMethodNotFoundException($"{benchmarkType}_{TestCase}", GetType().FullName);
|
||||
?? (Action?)GetType().GetMethod($"{benchmarkType}_AddRangeWhenSourceIs{benchmarkType}", BindingFlags.Instance | BindingFlags.Public)?.CreateDelegate(typeof(Action), this);
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ namespace Recyclable.Collections.Benchmarks.POC
|
||||
// [Params(RecyclableDefaults.MaxPooledBlockSize)]
|
||||
// [Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 10_000_000, 33_554_432, 50_000_000, 100_000_000)]
|
||||
[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072)]
|
||||
//[Params(RecyclableDefaults.MinItemsCountForParallelization, 1_048_576, 2_097_152, 4_194_304, 8_388_608, 16_777_216, 33_554_432, RecyclableDefaults.MaxPooledBlockSize)]
|
||||
// [Params(RecyclableDefaults.MinItemsCountForParallelization, 1_048_576, 2_097_152, 4_194_304, 8_388_608, 16_777_216, 33_554_432, RecyclableDefaults.MaxPooledBlockSize)]
|
||||
// [Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128)]
|
||||
// [Params(1, 10, 1024, 10240, RecyclableDefaults.MinPooledArrayLength, RecyclableDefaults.MinItemsCountForParallelization)]
|
||||
// [Params(RecyclableDefaults.MinItemsCountForParallelization * 3)]
|
||||
|
||||
@@ -6,7 +6,7 @@ using Recyclable.Collections.Benchmarks.POC;
|
||||
public static class Program
|
||||
{
|
||||
static IConfig BenchmarkConfig { get; } = ManualConfig.Create(DefaultConfig.Instance)
|
||||
.WithOptions(ConfigOptions.DisableOptimizationsValidator | ConfigOptions.JoinSummary | ConfigOptions.KeepBenchmarkFiles);
|
||||
.WithOptions(ConfigOptions.DisableOptimizationsValidator | ConfigOptions.JoinSummary );
|
||||
|
||||
static void RunAssemblyBenchmarks()
|
||||
{
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace Recyclable.Collections
|
||||
|
||||
protected T[] _memoryBlock;
|
||||
|
||||
public static explicit operator ReadOnlySpan<T>(RecyclableList<T> source) => new(source._memoryBlock, 0, source.Count);
|
||||
|
||||
private static void ThrowArgumentOutOfRangeException(in string message)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(message, (Exception?)null);
|
||||
@@ -92,6 +94,20 @@ namespace Recyclable.Collections
|
||||
AddRange(source);
|
||||
}
|
||||
|
||||
#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added
|
||||
public RecyclableList(RecyclableLongList<T> source)
|
||||
#pragma warning restore CS8618
|
||||
{
|
||||
AddRange(source);
|
||||
}
|
||||
|
||||
#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added
|
||||
public RecyclableList(ReadOnlySpan<T> source)
|
||||
#pragma warning restore CS8618
|
||||
{
|
||||
AddRange(source);
|
||||
}
|
||||
|
||||
#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added
|
||||
public RecyclableList(in T[] source)
|
||||
#pragma warning restore CS8618
|
||||
@@ -188,6 +204,26 @@ namespace Recyclable.Collections
|
||||
_count = targetCapacity;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public void AddRange(ReadOnlySpan<T> itemsSpan)
|
||||
{
|
||||
if (itemsSpan.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int sourceItemsCount = itemsSpan.Length;
|
||||
int targetCapacity = _count + sourceItemsCount;
|
||||
if (_capacity < targetCapacity)
|
||||
{
|
||||
_ = EnsureCapacity(this, targetCapacity);
|
||||
}
|
||||
|
||||
var targetSpan = new Span<T>(_memoryBlock, _count, sourceItemsCount);
|
||||
itemsSpan.CopyTo(targetSpan);
|
||||
_count = targetCapacity;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public void AddRange(List<T> items)
|
||||
{
|
||||
|
||||
@@ -417,6 +417,186 @@ namespace Recyclable.Collections
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling
|
||||
public RecyclableLongList(in T[] source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool<T[]>? memoryBlocksPool = default, ArrayPool<T>? blockArrayPool = default)
|
||||
#pragma warning restore CS8618
|
||||
{
|
||||
_memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool;
|
||||
if (expectedItemsCount > 0)
|
||||
{
|
||||
minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize));
|
||||
SetupBlockArrayPooling(this, minBlockSize, blockArrayPool);
|
||||
_capacity = Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value);
|
||||
if (minBlockSize != _memoryBlocks![0].Length && _capacity > 0)
|
||||
{
|
||||
SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool);
|
||||
_blockSizeMinus1 = _blockSize - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_blockSizeMinus1 = minBlockSize - 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool);
|
||||
_blockSizeMinus1 = _blockSize - 1;
|
||||
_memoryBlocks = _emptyMemoryBlocksArray;
|
||||
}
|
||||
|
||||
AddRange(source);
|
||||
}
|
||||
|
||||
#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling
|
||||
public RecyclableLongList(ReadOnlySpan<T> source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool<T[]>? memoryBlocksPool = default, ArrayPool<T>? blockArrayPool = default)
|
||||
#pragma warning restore CS8618
|
||||
{
|
||||
_memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool;
|
||||
if (expectedItemsCount > 0)
|
||||
{
|
||||
minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize));
|
||||
SetupBlockArrayPooling(this, minBlockSize, blockArrayPool);
|
||||
_capacity = Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value);
|
||||
if (minBlockSize != _memoryBlocks![0].Length && _capacity > 0)
|
||||
{
|
||||
SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool);
|
||||
_blockSizeMinus1 = _blockSize - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_blockSizeMinus1 = minBlockSize - 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool);
|
||||
_blockSizeMinus1 = _blockSize - 1;
|
||||
_memoryBlocks = _emptyMemoryBlocksArray;
|
||||
}
|
||||
|
||||
AddRange(source);
|
||||
}
|
||||
|
||||
#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling
|
||||
public RecyclableLongList(RecyclableList<T> source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool<T[]>? memoryBlocksPool = default, ArrayPool<T>? blockArrayPool = default)
|
||||
#pragma warning restore CS8618
|
||||
{
|
||||
_memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool;
|
||||
if (expectedItemsCount > 0)
|
||||
{
|
||||
minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize));
|
||||
SetupBlockArrayPooling(this, minBlockSize, blockArrayPool);
|
||||
_capacity = Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value);
|
||||
if (minBlockSize != _memoryBlocks![0].Length && _capacity > 0)
|
||||
{
|
||||
SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool);
|
||||
_blockSizeMinus1 = _blockSize - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_blockSizeMinus1 = minBlockSize - 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool);
|
||||
_blockSizeMinus1 = _blockSize - 1;
|
||||
_memoryBlocks = _emptyMemoryBlocksArray;
|
||||
}
|
||||
|
||||
AddRange(source);
|
||||
}
|
||||
|
||||
#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling
|
||||
public RecyclableLongList(RecyclableLongList<T> source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool<T[]>? memoryBlocksPool = default, ArrayPool<T>? blockArrayPool = default)
|
||||
#pragma warning restore CS8618
|
||||
{
|
||||
_memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool;
|
||||
if (expectedItemsCount > 0)
|
||||
{
|
||||
minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize));
|
||||
SetupBlockArrayPooling(this, minBlockSize, blockArrayPool);
|
||||
_capacity = Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value);
|
||||
if (minBlockSize != _memoryBlocks![0].Length && _capacity > 0)
|
||||
{
|
||||
SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool);
|
||||
_blockSizeMinus1 = _blockSize - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_blockSizeMinus1 = minBlockSize - 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool);
|
||||
_blockSizeMinus1 = _blockSize - 1;
|
||||
_memoryBlocks = _emptyMemoryBlocksArray;
|
||||
}
|
||||
|
||||
AddRange(source);
|
||||
}
|
||||
|
||||
#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling
|
||||
public RecyclableLongList(List<T> source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool<T[]>? memoryBlocksPool = default, ArrayPool<T>? blockArrayPool = default)
|
||||
#pragma warning restore CS8618
|
||||
{
|
||||
_memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool;
|
||||
if (expectedItemsCount > 0)
|
||||
{
|
||||
minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize));
|
||||
SetupBlockArrayPooling(this, minBlockSize, blockArrayPool);
|
||||
_capacity = Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value);
|
||||
if (minBlockSize != _memoryBlocks![0].Length && _capacity > 0)
|
||||
{
|
||||
SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool);
|
||||
_blockSizeMinus1 = _blockSize - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_blockSizeMinus1 = minBlockSize - 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool);
|
||||
_blockSizeMinus1 = _blockSize - 1;
|
||||
_memoryBlocks = _emptyMemoryBlocksArray;
|
||||
}
|
||||
|
||||
AddRange(source);
|
||||
}
|
||||
|
||||
#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling
|
||||
public RecyclableLongList(IList<T> source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool<T[]>? memoryBlocksPool = default, ArrayPool<T>? blockArrayPool = default)
|
||||
#pragma warning restore CS8618
|
||||
{
|
||||
_memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool;
|
||||
if (expectedItemsCount > 0)
|
||||
{
|
||||
minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize));
|
||||
SetupBlockArrayPooling(this, minBlockSize, blockArrayPool);
|
||||
_capacity = Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value);
|
||||
if (minBlockSize != _memoryBlocks![0].Length && _capacity > 0)
|
||||
{
|
||||
SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool);
|
||||
_blockSizeMinus1 = _blockSize - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_blockSizeMinus1 = minBlockSize - 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool);
|
||||
_blockSizeMinus1 = _blockSize - 1;
|
||||
_memoryBlocks = _emptyMemoryBlocksArray;
|
||||
}
|
||||
|
||||
AddRange(source);
|
||||
}
|
||||
|
||||
#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling
|
||||
public RecyclableLongList(IEnumerable<T> source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool<T[]>? memoryBlocksPool = default, ArrayPool<T>? blockArrayPool = default)
|
||||
#pragma warning restore CS8618
|
||||
@@ -576,6 +756,53 @@ namespace Recyclable.Collections
|
||||
_lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1);
|
||||
}
|
||||
|
||||
public void AddRange(ReadOnlySpan<T> itemsSpan)
|
||||
{
|
||||
if (itemsSpan.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
long targetCapacity = _longCount + itemsSpan.Length;
|
||||
if (_capacity < targetCapacity)
|
||||
{
|
||||
_ = EnsureCapacity(this, targetCapacity);
|
||||
}
|
||||
|
||||
int blockSize = _blockSize,
|
||||
targetBlockIndex = _nextItemBlockIndex,
|
||||
memoryBlockCount = _reservedBlockCount;
|
||||
|
||||
Span<T[]> memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount);
|
||||
Span<T> targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex);
|
||||
while (itemsSpan.Length >= targetBlockArraySpan.Length)
|
||||
{
|
||||
itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan);
|
||||
itemsSpan = itemsSpan[targetBlockArraySpan.Length..];
|
||||
if (itemsSpan.Length == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
targetBlockIndex++;
|
||||
targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize);
|
||||
}
|
||||
|
||||
if (itemsSpan.Length > 0)
|
||||
{
|
||||
itemsSpan.CopyTo(targetBlockArraySpan);
|
||||
_nextItemBlockIndex = targetBlockIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
_nextItemBlockIndex = targetBlockIndex + 1;
|
||||
}
|
||||
|
||||
_nextItemIndex = itemsSpan.Length;
|
||||
_longCount = targetCapacity;
|
||||
_lastBlockWithData = targetBlockIndex;
|
||||
}
|
||||
|
||||
public void AddRange(RecyclableLongList<T> items)
|
||||
{
|
||||
if (items.LongCount == 0)
|
||||
|
||||
Reference in new issue
Block a user