RecyclableList.IndexOf performance improvements.
This commit is contained in:
1 parent
209dac1808
commit
002e043b19
9 files changed
+206
-307
No files matched your search
@@ -5,7 +5,6 @@ namespace Recyclable.Collections
|
|||||||
public sealed class ItemRange
|
public sealed class ItemRange
|
||||||
{
|
{
|
||||||
public int BlockIndex;
|
public int BlockIndex;
|
||||||
public int StartingItemIndex;
|
|
||||||
public long ItemsToSearchCount;
|
public long ItemsToSearchCount;
|
||||||
internal bool _returned;
|
internal bool _returned;
|
||||||
|
|
||||||
@@ -13,11 +12,10 @@ namespace Recyclable.Collections
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public ItemRange(int blockIndex, int startingItemIndex, long itemsToSearchCount)
|
public ItemRange(int blockIndex, long itemsToSearchCount)
|
||||||
{
|
{
|
||||||
BlockIndex = blockIndex;
|
BlockIndex = blockIndex;
|
||||||
ItemsToSearchCount = itemsToSearchCount;
|
ItemsToSearchCount = itemsToSearchCount;
|
||||||
StartingItemIndex = startingItemIndex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|||||||
@@ -8,31 +8,22 @@ namespace Recyclable.Collections
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.NoInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.NoInlining)]
|
||||||
public static void Iterate<TList, T>(in ParallelSynchronizationContext context, in SearchConfiguration<TList, T> search, Func<ParallelSynchronizationContext, SearchConfiguration<TList, T>, ItemRange, bool> action)
|
public static void Iterate<TList, T>(in ParallelSynchronizationContext context, in SearchConfiguration<TList, T> search, Func<ParallelSynchronizationContext, SearchConfiguration<TList, T>, ItemRange, bool> action)
|
||||||
{
|
{
|
||||||
int itemIndex = 0;
|
|
||||||
ItemRange itemRange;
|
ItemRange itemRange;
|
||||||
int blockIndex = search.StartingBlockIndex;
|
int blockIndex = search.StartingBlockIndex;
|
||||||
long itemsCount = search.ItemsCount;
|
long itemsCount = search.ItemsCount;
|
||||||
while (itemsCount > search.Step)
|
while (itemsCount > search.BlockSize)
|
||||||
{
|
{
|
||||||
itemRange = new(blockIndex, itemIndex, search.Step);
|
itemRange = new(blockIndex, search.BlockSize);
|
||||||
if (!action(context, search, itemRange))
|
if (!action(context, search, itemRange))
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
itemsCount -= search.Step;
|
itemsCount -= search.BlockSize;
|
||||||
//blockIndex = (int)Math.DivRem(itemIndex + step, blockSize, out itemIndex);
|
blockIndex++;
|
||||||
blockIndex += (int)((itemIndex + search.Step) >> search.BlockSizePow2BitShift);
|
|
||||||
itemIndex = (int)((itemIndex + search.Step) & search.BlockSizeMinus1);
|
|
||||||
//itemIndex += step;
|
|
||||||
//while (itemIndex >= blockSize)
|
|
||||||
//{
|
|
||||||
// blockIndex++;
|
|
||||||
// itemIndex -= blockSize;
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
itemRange = new(blockIndex, itemIndex, itemsCount);
|
itemRange = new(blockIndex, itemsCount);
|
||||||
_ = action(context, search, itemRange);
|
_ = action(context, search, itemRange);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ namespace Recyclable.Collections.Parallel
|
|||||||
{
|
{
|
||||||
public sealed class ParallelSynchronizationContext : IDisposable
|
public sealed class ParallelSynchronizationContext : IDisposable
|
||||||
{
|
{
|
||||||
public readonly ManualResetEventSlimmer ItemFoundSignal;
|
internal bool _isItemFound;
|
||||||
|
|
||||||
public readonly Barrier AllDoneSignal;
|
public readonly Barrier AllDoneSignal;
|
||||||
public long FoundItemIndex;
|
public long FoundItemIndex;
|
||||||
@@ -11,16 +11,21 @@ namespace Recyclable.Collections.Parallel
|
|||||||
|
|
||||||
public ParallelSynchronizationContext()
|
public ParallelSynchronizationContext()
|
||||||
{
|
{
|
||||||
ItemFoundSignal = ManualResetEventSlimmerPool.Create(false);
|
|
||||||
AllDoneSignal = new(0);
|
AllDoneSignal = new(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ParallelSynchronizationContext(int participantCount)
|
public ParallelSynchronizationContext(int participantCount)
|
||||||
{
|
{
|
||||||
ItemFoundSignal = ManualResetEventSlimmerPool.Create(false);
|
|
||||||
AllDoneSignal = new(participantCount);
|
AllDoneSignal = new(participantCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetItemFound()
|
||||||
|
{
|
||||||
|
_isItemFound = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsItemFound => _isItemFound;
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (!_returned)
|
if (!_returned)
|
||||||
@@ -30,7 +35,6 @@ namespace Recyclable.Collections.Parallel
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ItemFoundSignal.Dispose();
|
|
||||||
AllDoneSignal.Dispose();
|
AllDoneSignal.Dispose();
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ namespace Recyclable.Collections.Parallel
|
|||||||
_updateLock.Exit(false);
|
_updateLock.Exit(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.ItemFoundSignal._isSet)
|
if (context._isItemFound)
|
||||||
{
|
{
|
||||||
context.ItemFoundSignal.Reset();
|
context._isItemFound = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (context.AllDoneSignal.ParticipantsRemaining)
|
switch (context.AllDoneSignal.ParticipantsRemaining)
|
||||||
@@ -40,7 +40,7 @@ namespace Recyclable.Collections.Parallel
|
|||||||
|
|
||||||
if (context.FoundItemIndex >= 0)
|
if (context.FoundItemIndex >= 0)
|
||||||
{
|
{
|
||||||
context.FoundItemIndex = -1L;
|
context.FoundItemIndex = RecyclableDefaults.ItemNotFoundIndexLong;
|
||||||
}
|
}
|
||||||
|
|
||||||
context._returned = false;
|
context._returned = false;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace Recyclable.Collections.Pools
|
|||||||
private static readonly SpinLock _updateLock = new(false);
|
private static readonly SpinLock _updateLock = new(false);
|
||||||
public static readonly ObjectPool<ItemRange> Shared = new DefaultObjectPool<ItemRange>(new DefaultPooledObjectPolicy<ItemRange>());
|
public static readonly ObjectPool<ItemRange> Shared = new DefaultObjectPool<ItemRange>(new DefaultPooledObjectPolicy<ItemRange>());
|
||||||
|
|
||||||
public static ItemRange Create(int blockIndex, int startingItemIndex, long itemsToSearchCount)
|
public static ItemRange Create(int blockIndex, long itemsToSearchCount)
|
||||||
{
|
{
|
||||||
bool lockTaken = false;
|
bool lockTaken = false;
|
||||||
_updateLock.Enter(ref lockTaken);
|
_updateLock.Enter(ref lockTaken);
|
||||||
@@ -20,7 +20,6 @@ namespace Recyclable.Collections.Pools
|
|||||||
itemRange._returned = false;
|
itemRange._returned = false;
|
||||||
itemRange.BlockIndex = blockIndex;
|
itemRange.BlockIndex = blockIndex;
|
||||||
itemRange.ItemsToSearchCount = itemsToSearchCount;
|
itemRange.ItemsToSearchCount = itemsToSearchCount;
|
||||||
itemRange.StartingItemIndex = startingItemIndex;
|
|
||||||
return itemRange;
|
return itemRange;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,10 @@
|
|||||||
{
|
{
|
||||||
public const int BlockSize = 16_384;
|
public const int BlockSize = 16_384;
|
||||||
public const int Capacity = 1;
|
public const int Capacity = 1;
|
||||||
|
public const long MinItemsCountForParallelization = 786_432;
|
||||||
public const int MinPooledArrayLength = 128;
|
public const int MinPooledArrayLength = 128;
|
||||||
public const int MaxPooledBlockSize = 2_147_483_591;
|
public const int MaxPooledBlockSize = 2_147_483_591;
|
||||||
|
public const long ItemNotFoundIndexLong = -1L;
|
||||||
|
public const int ItemNotFoundIndex = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,12 +9,8 @@ using Recyclable.Collections.Pools;
|
|||||||
|
|
||||||
namespace Recyclable.Collections
|
namespace Recyclable.Collections
|
||||||
{
|
{
|
||||||
public class RecyclableList<T> : IDisposable, IList<T>
|
public partial class RecyclableList<T> : IDisposable, IList<T>
|
||||||
{
|
{
|
||||||
private const long ItemNotFoundIndexLong = -1L;
|
|
||||||
private const int ItemNotFoundIndex = -1;
|
|
||||||
private const double OptimalParallelSearchStep = 0.1;// 0.329;
|
|
||||||
|
|
||||||
private static readonly ArrayPool<T[]> _defaultMemoryBlocksPool = ArrayPool<T[]>.Create();
|
private static readonly ArrayPool<T[]> _defaultMemoryBlocksPool = ArrayPool<T[]>.Create();
|
||||||
private static readonly ArrayPool<T> _defaultBlockArrayPool = ArrayPool<T>.Create();
|
private static readonly ArrayPool<T> _defaultBlockArrayPool = ArrayPool<T>.Create();
|
||||||
private static readonly T[][] _emptyMemoryBlocksArray = new T[0][];
|
private static readonly T[][] _emptyMemoryBlocksArray = new T[0][];
|
||||||
@@ -24,7 +20,7 @@ namespace Recyclable.Collections
|
|||||||
internal byte _blockSizePow2BitShift;
|
internal byte _blockSizePow2BitShift;
|
||||||
internal int _blockSizeMinus1;
|
internal int _blockSizeMinus1;
|
||||||
private int _nextItemBlockIndex;
|
private int _nextItemBlockIndex;
|
||||||
private int _nextItemIndex;
|
internal int _nextItemIndex;
|
||||||
private int _reservedBlockCount;
|
private int _reservedBlockCount;
|
||||||
|
|
||||||
protected static readonly bool NeedsClearing = !typeof(T).IsValueType;
|
protected static readonly bool NeedsClearing = !typeof(T).IsValueType;
|
||||||
@@ -42,19 +38,15 @@ namespace Recyclable.Collections
|
|||||||
|
|
||||||
public int Count => checked((int)_longCount);
|
public int Count => checked((int)_longCount);
|
||||||
public bool IsReadOnly { get; }
|
public bool IsReadOnly { get; }
|
||||||
public int LastBlockWithData => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1);
|
private int _lastBlockWithData;
|
||||||
|
public int LastBlockWithData => _lastBlockWithData;
|
||||||
|
|
||||||
internal long _longCountIndexOfStep;
|
|
||||||
internal long _longCount;
|
internal long _longCount;
|
||||||
|
|
||||||
public long LongCount
|
public long LongCount
|
||||||
{
|
{
|
||||||
get => _longCount;
|
get => _longCount;
|
||||||
set
|
set => _longCount = value;
|
||||||
{
|
|
||||||
_longCount = value;
|
|
||||||
_longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int ReservedBlockCount => _reservedBlockCount;
|
public int ReservedBlockCount => _reservedBlockCount;
|
||||||
@@ -75,7 +67,7 @@ namespace Recyclable.Collections
|
|||||||
int targetBlockIndex = (int)(index >> list._blockSizePow2BitShift);
|
int targetBlockIndex = (int)(index >> list._blockSizePow2BitShift);
|
||||||
int targetItemIndex = (int)(index & list._blockSizeMinus1);
|
int targetItemIndex = (int)(index & list._blockSizeMinus1);
|
||||||
|
|
||||||
int lastTakenBlockIndex = list.LastBlockWithData;
|
int lastTakenBlockIndex = list._lastBlockWithData;
|
||||||
while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != list._nextItemBlockIndex)))
|
while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != list._nextItemBlockIndex)))
|
||||||
{
|
{
|
||||||
int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0
|
int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0
|
||||||
@@ -150,6 +142,8 @@ namespace Recyclable.Collections
|
|||||||
|
|
||||||
if (!enumerator.MoveNext())
|
if (!enumerator.MoveNext())
|
||||||
{
|
{
|
||||||
|
_lastBlockWithData = targetBlockIdx;
|
||||||
|
_longCount += copied + i - _longCount;
|
||||||
if (targetItemIdx < blockSize)
|
if (targetItemIdx < blockSize)
|
||||||
{
|
{
|
||||||
_nextItemBlockIndex = targetBlockIdx;
|
_nextItemBlockIndex = targetBlockIdx;
|
||||||
@@ -161,8 +155,6 @@ namespace Recyclable.Collections
|
|||||||
_nextItemIndex = 0;
|
_nextItemIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
_longCount += copied + i - _longCount;
|
|
||||||
_longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,153 +212,9 @@ namespace Recyclable.Collections
|
|||||||
}
|
}
|
||||||
|
|
||||||
_longCount = requiredCapacity;
|
_longCount = requiredCapacity;
|
||||||
_longCountIndexOfStep = Math.Max((long)(requiredCapacity * OptimalParallelSearchStep), 1L);
|
|
||||||
_nextItemBlockIndex = targetBlockIdx;
|
_nextItemBlockIndex = targetBlockIdx;
|
||||||
_nextItemIndex = targetItemIdx;
|
_nextItemIndex = targetItemIdx;
|
||||||
}
|
_lastBlockWithData = targetBlockIdx - (targetItemIdx > 0 ? 0 : 1);
|
||||||
|
|
||||||
// & WAS SLOWER
|
|
||||||
// [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static long DoLongIndexOfParallel(RecyclableList<T> list, in ItemRange itemRange, in T itemToFind, in ManualResetEventSlimmer itemFoundSignal)
|
|
||||||
{
|
|
||||||
// & WAS SLOWER without
|
|
||||||
int blockSize = list._blockSize;
|
|
||||||
int blockIndex = itemRange.BlockIndex;
|
|
||||||
long itemsToSearchCount = itemRange.ItemsToSearchCount;
|
|
||||||
int index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, itemRange.StartingItemIndex, (int)Math.Min(blockSize - itemRange.StartingItemIndex, itemsToSearchCount));
|
|
||||||
if (itemFoundSignal.IsSet)
|
|
||||||
{
|
|
||||||
return ItemNotFoundIndexLong;
|
|
||||||
}
|
|
||||||
else if (index >= 0)
|
|
||||||
{
|
|
||||||
return (blockIndex << list._blockSizePow2BitShift) + index;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't need Math.Min here, because if itemsToSearchCount < blockSize - itemRange.StartingItemIndex, then it would mean that
|
|
||||||
// there are no more items and we already scanned all. We want to break the loop. Subtracting a higher value from itemsToSearchCount
|
|
||||||
// will always result in a negative result.
|
|
||||||
itemsToSearchCount -= blockSize - itemRange.StartingItemIndex;
|
|
||||||
if (itemsToSearchCount <= 0)
|
|
||||||
{
|
|
||||||
return ItemNotFoundIndexLong;
|
|
||||||
}
|
|
||||||
|
|
||||||
//T itemToFind = itemRange.ItemToFind;
|
|
||||||
//int lastBlockWithData = itemRange.LastBlockWithData;
|
|
||||||
blockIndex++;
|
|
||||||
while (itemsToSearchCount > blockSize)
|
|
||||||
{
|
|
||||||
index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, blockSize);
|
|
||||||
if (itemFoundSignal.IsSet)
|
|
||||||
{
|
|
||||||
return ItemNotFoundIndexLong;
|
|
||||||
}
|
|
||||||
else if (index >= 0)
|
|
||||||
{
|
|
||||||
return ((long)blockIndex << list._blockSizePow2BitShift) + index;
|
|
||||||
}
|
|
||||||
|
|
||||||
itemsToSearchCount -= blockSize;
|
|
||||||
blockIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
index = itemsToSearchCount <= 0 //|| blockIndex > list.LastBlockWithData
|
|
||||||
? ItemNotFoundIndex
|
|
||||||
// : blockIndex == list._nextItemBlockIndex
|
|
||||||
// ? Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount)
|
|
||||||
: Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount);
|
|
||||||
// ? Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, checked((int)Math.Min(itemRange.List._nextItemIndex, itemsToSearchCount)))
|
|
||||||
//: Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, checked((int)Math.Min(blockSize, itemsToSearchCount)));
|
|
||||||
|
|
||||||
return !itemFoundSignal.IsSet && index >= 0 ? ((long)blockIndex << list._blockSizePow2BitShift) + index : ItemNotFoundIndexLong;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
||||||
private long DoIndexOfParallel(in T item)
|
|
||||||
{
|
|
||||||
if (_longCount == 0)
|
|
||||||
{
|
|
||||||
return ItemNotFoundIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var context = ParallelSynchronizationContextPool.GetWithOneParticipant();
|
|
||||||
// (long)(_longCount * 0.329) => most efficient step, based on benchmarks
|
|
||||||
Iterate(context, item);
|
|
||||||
context.AllDoneSignal.SignalAndWait();
|
|
||||||
if (context.Exception == null)
|
|
||||||
{
|
|
||||||
return context.FoundItemIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.Exception.CaptureAndRethrow();
|
|
||||||
return ItemNotFoundIndexLong;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
||||||
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
// [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.NoOptimization)]
|
|
||||||
private static bool ScheduleIndexOfTask(ParallelSynchronizationContext context, RecyclableList<T> list, T itemToFind, ItemRange itemRange)
|
|
||||||
{
|
|
||||||
_ = context.AllDoneSignal.AddParticipant();
|
|
||||||
// & WAS SLOWER
|
|
||||||
// _ = ThreadPool.QueueUserWorkItem<object?>((_) =>
|
|
||||||
_ = Task.Factory.StartNew(() =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (context.ItemFoundSignal.IsSet)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var index = DoLongIndexOfParallel(list, itemRange, itemToFind, context.ItemFoundSignal);
|
|
||||||
if (index >= 0)
|
|
||||||
{
|
|
||||||
context.ItemFoundSignal.Set();
|
|
||||||
_ = Interlocked.Exchange(ref context.FoundItemIndex, index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_ = Interlocked.Exchange(ref context.Exception, e);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
context.AllDoneSignal.RemoveParticipant();
|
|
||||||
itemRange.Dispose();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return !context.ItemFoundSignal.IsSet;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
||||||
protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex)
|
|
||||||
{
|
|
||||||
Span<T[]> memoryBlocksSpan = new(memoryBlocks, 0, lastBlockIndex + 1);
|
|
||||||
int itemIndex, blockIndex;
|
|
||||||
for (blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++)
|
|
||||||
{
|
|
||||||
itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize);
|
|
||||||
if (itemIndex >= 0)
|
|
||||||
{
|
|
||||||
return itemIndex + ((long)blockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (blockIndex == lastBlockIndex)
|
|
||||||
{
|
|
||||||
itemIndex = nextItemIndex != 0
|
|
||||||
? Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, nextItemIndex)
|
|
||||||
: Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, blockSize);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return ItemNotFoundIndexLong;
|
|
||||||
}
|
|
||||||
|
|
||||||
return itemIndex >= 0 ? itemIndex + ((long)lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndexLong;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -482,54 +330,32 @@ namespace Recyclable.Collections
|
|||||||
// [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.NoOptimization)]
|
// [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.NoOptimization)]
|
||||||
// & WAS SLOWER
|
// & WAS SLOWER
|
||||||
// [MethodImpl(MethodImplOptions.NoInlining)]
|
// [MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
private void Iterate(in ParallelSynchronizationContext context, in T itemToFind)
|
//private static void Iterate(RecyclableList<T> list, in ParallelSynchronizationContext context, in T itemToFind)
|
||||||
{
|
//{
|
||||||
// TODO: Switch to use _blockSize instead of list._longCountIndeexOfStep for simplifications & better performance.
|
// // TODO: Switch to use _blockSize instead of list._longCountIndeexOfStep for simplifications & better performance.
|
||||||
|
|
||||||
// & WAS SLOWER without
|
// // & WAS SLOWER without
|
||||||
ItemRange itemRange;
|
// int blockIndex = 0;
|
||||||
int blockIndex = 0;
|
|
||||||
// & WAS SLOWER
|
|
||||||
// int blockSize = _blockSize;
|
|
||||||
long itemIndex = 0;
|
|
||||||
long itemsCount = _longCount;
|
|
||||||
// & WAS SLOWER
|
|
||||||
// int blockSize = _blockSize;
|
|
||||||
// long step = list._longCountIndexOfStep;
|
|
||||||
// & WAS SLOWER
|
|
||||||
// while ((blockIndex << list._blockSizePow2BitShift) + itemIndex > step)
|
|
||||||
// & WAS SLOWER
|
|
||||||
// long longCountIndexOfStep = _longCountIndexOfStep;
|
|
||||||
while (itemsCount > _longCountIndexOfStep)
|
|
||||||
{
|
|
||||||
// At this point itemIndex is limited to blockSize range - int.
|
|
||||||
itemRange = ItemRangePool.Create(blockIndex, (int)itemIndex, _longCountIndexOfStep);
|
|
||||||
if (!ScheduleIndexOfTask(context, this, itemToFind, itemRange))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
itemsCount -= _longCountIndexOfStep;
|
// long itemsCount = list._longCount;
|
||||||
|
// // & WAS SLOWER
|
||||||
|
// // int blockSize = _blockSize;
|
||||||
|
// // & WAS SLOWER
|
||||||
|
// // while ((blockIndex << list._blockSizePow2BitShift) + itemIndex > step)
|
||||||
|
// while (itemsCount > list._blockSize)
|
||||||
|
// {
|
||||||
|
// // At this point itemIndex is limited to blockSize range - int.
|
||||||
|
// if (!ScheduleIndexOfTask(context, list, blockIndex, list._blockSize, itemToFind))
|
||||||
|
// {
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
|
||||||
// & WAS SLOWER
|
// itemsCount -= list._blockSize;
|
||||||
// blockIndex = (int)Math.DivRem(itemIndex + step, step, out itemIndex);
|
// blockIndex++;
|
||||||
// & WAS SLOWER
|
// }
|
||||||
// blockIndex += (int)((itemIndex + list._longCountIndexOfStep) >> list._blockSizePow2BitShift);
|
|
||||||
// itemIndex = (int)((itemIndex + list._longCountIndexOfStep) & list._blockSizeMinus1);
|
|
||||||
|
|
||||||
itemIndex += _longCountIndexOfStep;
|
// _ = ScheduleIndexOfTask(context, list, blockIndex, (int)itemsCount, itemToFind);
|
||||||
while (itemIndex >= _blockSize)
|
//}
|
||||||
{
|
|
||||||
blockIndex++;
|
|
||||||
itemIndex -= _blockSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// & WAS SLOWER
|
|
||||||
// itemRange = new(blockIndex, (int)itemIndex, list._longCount - (blockIndex << list._blockSizePow2BitShift) - itemIndex);
|
|
||||||
itemRange = ItemRangePool.Create(blockIndex, (int)itemIndex, itemsCount);
|
|
||||||
_ = ScheduleIndexOfTask(context, this, itemToFind, itemRange);
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
protected static void SetupBlockArrayPooling(RecyclableList<T> list, int blockSize, ArrayPool<T>? blockArrayPool = null)
|
protected static void SetupBlockArrayPooling(RecyclableList<T> list, int blockSize, ArrayPool<T>? blockArrayPool = null)
|
||||||
@@ -648,9 +474,12 @@ namespace Recyclable.Collections
|
|||||||
_nextItemBlockIndex++;
|
_nextItemBlockIndex++;
|
||||||
_nextItemIndex = 0;
|
_nextItemIndex = 0;
|
||||||
}
|
}
|
||||||
|
else if (_nextItemIndex == 1)
|
||||||
|
{
|
||||||
|
_lastBlockWithData++;
|
||||||
|
}
|
||||||
|
|
||||||
_longCount++;
|
_longCount++;
|
||||||
_longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddRange(in T[] items)
|
public void AddRange(in T[] items)
|
||||||
@@ -693,8 +522,8 @@ namespace Recyclable.Collections
|
|||||||
|
|
||||||
_nextItemIndex = itemsSpan.Length;
|
_nextItemIndex = itemsSpan.Length;
|
||||||
_longCount = targetCapacity;
|
_longCount = targetCapacity;
|
||||||
_longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L);
|
|
||||||
_nextItemBlockIndex = targetBlockIndex;
|
_nextItemBlockIndex = targetBlockIndex;
|
||||||
|
_lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddRange(RecyclableArrayList<T> items)
|
public void AddRange(RecyclableArrayList<T> items)
|
||||||
@@ -733,8 +562,8 @@ namespace Recyclable.Collections
|
|||||||
_nextItemIndex = itemsSpan.Length;
|
_nextItemIndex = itemsSpan.Length;
|
||||||
itemsSpan.CopyTo(targetBlockArraySpan);
|
itemsSpan.CopyTo(targetBlockArraySpan);
|
||||||
_longCount = targetCapacity;
|
_longCount = targetCapacity;
|
||||||
_longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L);
|
|
||||||
_nextItemBlockIndex = targetBlockIndex;
|
_nextItemBlockIndex = targetBlockIndex;
|
||||||
|
_lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddRange(RecyclableList<T> items)
|
public void AddRange(RecyclableList<T> items)
|
||||||
@@ -794,6 +623,7 @@ namespace Recyclable.Collections
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_lastBlockWithData = targetBlockIndex;
|
||||||
if ((targetCapacity & _blockSizeMinus1) == 0)
|
if ((targetCapacity & _blockSizeMinus1) == 0)
|
||||||
{
|
{
|
||||||
_nextItemIndex = 0;
|
_nextItemIndex = 0;
|
||||||
@@ -806,7 +636,6 @@ namespace Recyclable.Collections
|
|||||||
}
|
}
|
||||||
|
|
||||||
_longCount = targetCapacity;
|
_longCount = targetCapacity;
|
||||||
_longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddRange(List<T> items)
|
public void AddRange(List<T> items)
|
||||||
@@ -853,7 +682,7 @@ namespace Recyclable.Collections
|
|||||||
|
|
||||||
_nextItemBlockIndex = targetBlockIndex;
|
_nextItemBlockIndex = targetBlockIndex;
|
||||||
_longCount = targetCapacity;
|
_longCount = targetCapacity;
|
||||||
_longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L);
|
_lastBlockWithData = targetBlockIndex - (_nextItemIndex > 0 ? 0 : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddRange(IList<T> items)
|
public void AddRange(IList<T> items)
|
||||||
@@ -898,9 +727,9 @@ namespace Recyclable.Collections
|
|||||||
|
|
||||||
itemsSpan.CopyTo(targetBlockArraySpan);
|
itemsSpan.CopyTo(targetBlockArraySpan);
|
||||||
_longCount = targetCapacity;
|
_longCount = targetCapacity;
|
||||||
_longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L);
|
|
||||||
_nextItemBlockIndex = targetBlockIndex;
|
_nextItemBlockIndex = targetBlockIndex;
|
||||||
_nextItemIndex = itemsSpan.Length;
|
_nextItemIndex = itemsSpan.Length;
|
||||||
|
_lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -976,8 +805,8 @@ namespace Recyclable.Collections
|
|||||||
_reservedBlockCount = 0;
|
_reservedBlockCount = 0;
|
||||||
_nextItemBlockIndex = 0;
|
_nextItemBlockIndex = 0;
|
||||||
_nextItemIndex = 0;
|
_nextItemIndex = 0;
|
||||||
|
_lastBlockWithData = RecyclableDefaults.ItemNotFoundIndex;
|
||||||
_longCount = 0;
|
_longCount = 0;
|
||||||
_longCountIndexOfStep = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
@@ -1010,17 +839,81 @@ namespace Recyclable.Collections
|
|||||||
|
|
||||||
public IEnumerator<T> GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator();
|
public IEnumerator<T> GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator();
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
public int IndexOf(T item) => checked((int)DoIndexOfParallel(item));
|
public int IndexOf(T item)
|
||||||
|
{
|
||||||
|
if (_lastBlockWithData == 0)
|
||||||
|
{
|
||||||
|
return Array.IndexOf(_memoryBlocks[0], item, 0, _longCount < _blockSize ? _nextItemIndex : _blockSize);
|
||||||
|
}
|
||||||
|
else if (_longCount == 0)
|
||||||
|
{
|
||||||
|
return RecyclableDefaults.ItemNotFoundIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
int itemIndex = Array.IndexOf(_memoryBlocks[0], item, 0, _longCount < _blockSize ? _nextItemIndex : _blockSize);
|
||||||
|
if (itemIndex >= 0)
|
||||||
|
{
|
||||||
|
return itemIndex;
|
||||||
|
}
|
||||||
|
else if (_longCount < RecyclableDefaults.MinItemsCountForParallelization)
|
||||||
|
{
|
||||||
|
return (int)RecyclableListIndexOfHelpers.IndexOfSequential(this, item);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// & WAS SLOWER
|
||||||
|
// long toSearch = _longCount - _blockSize;
|
||||||
|
if (_longCount > _blockSize)
|
||||||
|
{
|
||||||
|
itemIndex = Array.IndexOf(_memoryBlocks[1], item, 0, (int)Math.Min(_blockSize, _longCount - _blockSize));
|
||||||
|
return itemIndex >= 0 ? _blockSize + itemIndex : (int)RecyclableListIndexOfHelpers.IndexOfParallel(this, item);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return RecyclableDefaults.ItemNotFoundIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Insert(int index, T item) => throw new NotSupportedException();
|
public void Insert(int index, T item) => throw new NotSupportedException();
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
public long LongIndexOf(T item) => _longCount == 0
|
public long LongIndexOf(T item)
|
||||||
? ItemNotFoundIndexLong
|
{
|
||||||
: _longCount <= _blockSize
|
if (_lastBlockWithData == 0)
|
||||||
? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount))
|
{
|
||||||
: DoIndexOfParallel(item);
|
return Array.IndexOf(_memoryBlocks[0], item, 0, _longCount < _blockSize ? _nextItemIndex : _blockSize);
|
||||||
|
}
|
||||||
|
else if (_longCount == 0)
|
||||||
|
{
|
||||||
|
return RecyclableDefaults.ItemNotFoundIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
int itemIndex = Array.IndexOf(_memoryBlocks[0], item, 0, _longCount < _blockSize ? _nextItemIndex : _blockSize);
|
||||||
|
if (itemIndex >= 0)
|
||||||
|
{
|
||||||
|
return itemIndex;
|
||||||
|
}
|
||||||
|
else if (_longCount < RecyclableDefaults.MinItemsCountForParallelization)
|
||||||
|
{
|
||||||
|
return RecyclableListIndexOfHelpers.IndexOfSequential(this, item);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// & WAS SLOWER
|
||||||
|
// long toSearch = _longCount - _blockSize;
|
||||||
|
if (_longCount > _blockSize)
|
||||||
|
{
|
||||||
|
itemIndex = Array.IndexOf(_memoryBlocks[1], item, 0, (int)Math.Min(_blockSize, _longCount - _blockSize));
|
||||||
|
return itemIndex >= 0 ? _blockSize + itemIndex : RecyclableListIndexOfHelpers.IndexOfParallel(this, item);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return RecyclableDefaults.ItemNotFoundIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
public bool Remove(T item)
|
public bool Remove(T item)
|
||||||
@@ -1029,7 +922,6 @@ namespace Recyclable.Collections
|
|||||||
if (index >= 0)
|
if (index >= 0)
|
||||||
{
|
{
|
||||||
_longCount--;
|
_longCount--;
|
||||||
_longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L);
|
|
||||||
|
|
||||||
if (index < _longCount)
|
if (index < _longCount)
|
||||||
{
|
{
|
||||||
@@ -1039,6 +931,10 @@ namespace Recyclable.Collections
|
|||||||
if (_nextItemIndex > 0)
|
if (_nextItemIndex > 0)
|
||||||
{
|
{
|
||||||
_nextItemIndex--;
|
_nextItemIndex--;
|
||||||
|
if (_nextItemIndex == 0)
|
||||||
|
{
|
||||||
|
_lastBlockWithData--;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1080,6 +976,8 @@ namespace Recyclable.Collections
|
|||||||
{
|
{
|
||||||
_nextItemBlockIndex--;
|
_nextItemBlockIndex--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_lastBlockWithData--;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
@@ -1091,7 +989,6 @@ namespace Recyclable.Collections
|
|||||||
}
|
}
|
||||||
|
|
||||||
_longCount--;
|
_longCount--;
|
||||||
_longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L);
|
|
||||||
|
|
||||||
if (index < _longCount)
|
if (index < _longCount)
|
||||||
{
|
{
|
||||||
@@ -1101,6 +998,10 @@ namespace Recyclable.Collections
|
|||||||
if (_nextItemIndex > 0)
|
if (_nextItemIndex > 0)
|
||||||
{
|
{
|
||||||
_nextItemIndex--;
|
_nextItemIndex--;
|
||||||
|
if (_nextItemIndex == 0)
|
||||||
|
{
|
||||||
|
_lastBlockWithData--;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1125,7 +1026,6 @@ namespace Recyclable.Collections
|
|||||||
}
|
}
|
||||||
|
|
||||||
_longCount--;
|
_longCount--;
|
||||||
_longCountIndexOfStep = Math.Max((long)Math.Floor(_longCount * OptimalParallelSearchStep), 1L);
|
|
||||||
|
|
||||||
if (index < _longCount)
|
if (index < _longCount)
|
||||||
{
|
{
|
||||||
@@ -1135,6 +1035,10 @@ namespace Recyclable.Collections
|
|||||||
if (_nextItemIndex > 0)
|
if (_nextItemIndex > 0)
|
||||||
{
|
{
|
||||||
_nextItemIndex--;
|
_nextItemIndex--;
|
||||||
|
if (_nextItemIndex == 0)
|
||||||
|
{
|
||||||
|
_lastBlockWithData--;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace Recyclable.Collections
|
|||||||
public byte BlockSizePow2BitShift { get; init; }
|
public byte BlockSizePow2BitShift { get; init; }
|
||||||
public long ItemsCount { get; init; }
|
public long ItemsCount { get; init; }
|
||||||
public int StartingBlockIndex { get; init; }
|
public int StartingBlockIndex { get; init; }
|
||||||
public long Step { get; init; }
|
public long BlockSize { get; init; }
|
||||||
|
|
||||||
// public SearchConfiguration(TList list, in TItem itemToFind)
|
// public SearchConfiguration(TList list, in TItem itemToFind)
|
||||||
// {
|
// {
|
||||||
@@ -23,7 +23,7 @@ namespace Recyclable.Collections
|
|||||||
BlockSizeMinus1 = list._blockSizeMinus1,
|
BlockSizeMinus1 = list._blockSizeMinus1,
|
||||||
BlockSizePow2BitShift = list._blockSizePow2BitShift,
|
BlockSizePow2BitShift = list._blockSizePow2BitShift,
|
||||||
ItemsCount = list._longCount,
|
ItemsCount = list._longCount,
|
||||||
Step = list._longCountIndexOfStep
|
BlockSize = list._blockSize
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,7 @@ namespace Recyclable.CollectionsTests
|
|||||||
{
|
{
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(TestCases))]
|
[MemberData(nameof(TestCases))]
|
||||||
public void IterateShouldYieldAllItems(int startingBlockIndex, int blockSize, long itemsCount, int step, IEnumerable<(int BlockIndex, int StartingItemIndex, long ItemsCount)> expected)
|
public void IterateShouldYieldAllItems(int startingBlockIndex, int blockSize, long itemsCount, int step, IEnumerable<(int BlockIndex, long ItemsCount)> expected)
|
||||||
{
|
{
|
||||||
// Prepare
|
// Prepare
|
||||||
var mockList = new RecyclableList<long>();
|
var mockList = new RecyclableList<long>();
|
||||||
@@ -23,15 +23,15 @@ namespace Recyclable.CollectionsTests
|
|||||||
BlockSizeMinus1 = blockSize - 1,
|
BlockSizeMinus1 = blockSize - 1,
|
||||||
BlockSizePow2BitShift = blockSizePow2BitShift,
|
BlockSizePow2BitShift = blockSizePow2BitShift,
|
||||||
ItemsCount = itemsCount,
|
ItemsCount = itemsCount,
|
||||||
Step = step,
|
BlockSize = step,
|
||||||
};
|
};
|
||||||
|
|
||||||
var actualItemRanges = new List<(int BlockIndex, int StartingItemIndex, long ItemsCount)>();
|
var actualItemRanges = new List<(int BlockIndex, long ItemsCount)>();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
ItemRangesIterator.Iterate(synchronizationContext, search, (context, search, itemRange) =>
|
ItemRangesIterator.Iterate(synchronizationContext, search, (context, search, itemRange) =>
|
||||||
{
|
{
|
||||||
actualItemRanges.Add((itemRange.BlockIndex, itemRange.StartingItemIndex, itemRange.ItemsToSearchCount));
|
actualItemRanges.Add((itemRange.BlockIndex, itemRange.ItemsToSearchCount));
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -43,102 +43,102 @@ namespace Recyclable.CollectionsTests
|
|||||||
{
|
{
|
||||||
new object[]
|
new object[]
|
||||||
{
|
{
|
||||||
0, 128, 333, (long)(333 * 0.329), new(int BlockIndex, int StartingItemIndex, long ItemsCount)[]
|
0, 128, 333, (long)(333 * 0.329), new(int BlockIndex, long ItemsCount)[]
|
||||||
{
|
{
|
||||||
(0, 0, 109),
|
(0, 109),
|
||||||
(0, 109, 109),
|
(0, 109),
|
||||||
(1, 90, 109),
|
(1, 109),
|
||||||
(2, 71, 6)
|
(2, 6)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
new object[]
|
new object[]
|
||||||
{
|
{
|
||||||
0, 2, 10, 2, new (int BlockIndex, int StartingItemIndex, long ItemsCount)[]
|
0, 2, 10, 2, new (int BlockIndex, long ItemsCount)[]
|
||||||
{
|
{
|
||||||
(0, 0, 2),
|
(0, 2),
|
||||||
(1, 0, 2),
|
(1, 2),
|
||||||
(2, 0, 2),
|
(2, 2),
|
||||||
(3, 0, 2),
|
(3, 2),
|
||||||
(4, 0, 2),
|
(4, 2),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
new object[]
|
new object[]
|
||||||
{
|
{
|
||||||
0, 2, 10, 3, new (int BlockIndex, int StartingItemIndex, long ItemsCount)[]
|
0, 2, 10, 3, new (int BlockIndex, long ItemsCount)[]
|
||||||
{
|
{
|
||||||
(0, 0, 3),
|
(0, 3),
|
||||||
(1, 1, 3),
|
(1, 3),
|
||||||
(3, 0, 3),
|
(3, 3),
|
||||||
(4, 1, 1),
|
(4, 1),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
new object[]
|
new object[]
|
||||||
{
|
{
|
||||||
0, 1, 10, 3, new (int BlockIndex, int StartingItemIndex, long ItemsCount)[]
|
0, 1, 10, 3, new (int BlockIndex, long ItemsCount)[]
|
||||||
{
|
{
|
||||||
(0, 0, 3),
|
(0, 3),
|
||||||
(3, 0, 3),
|
(3, 3),
|
||||||
(6, 0, 3),
|
(6, 3),
|
||||||
(9, 0, 1),
|
(9, 1),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
new object[]
|
new object[]
|
||||||
{
|
{
|
||||||
0, 2, 10, 4, new (int BlockIndex, int StartingItemIndex, long ItemsCount)[]
|
0, 2, 10, 4, new (int BlockIndex, long ItemsCount)[]
|
||||||
{
|
{
|
||||||
(0, 0, 4),
|
(0, 4),
|
||||||
(2, 0, 4),
|
(2, 4),
|
||||||
(4, 0, 2),
|
(4, 2),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
new object[]
|
new object[]
|
||||||
{
|
{
|
||||||
0, 1, 10, 2, new (int BlockIndex, int StartingItemIndex, long ItemsCount)[]
|
0, 1, 10, 2, new (int BlockIndex, long ItemsCount)[]
|
||||||
{
|
{
|
||||||
(0, 0, 2),
|
(0, 2),
|
||||||
(2, 0, 2),
|
(2, 2),
|
||||||
(4, 0, 2),
|
(4, 2),
|
||||||
(6, 0, 2),
|
(6, 2),
|
||||||
(8, 0, 2),
|
(8, 2),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
new object[]
|
new object[]
|
||||||
{
|
{
|
||||||
0, 2, 10, 1, new (int BlockIndex, int StartingItemIndex, long ItemsCount)[]
|
0, 2, 10, 1, new (int BlockIndex, long ItemsCount)[]
|
||||||
{
|
{
|
||||||
(0, 0, 1),
|
(0, 1),
|
||||||
(0, 1, 1),
|
(0, 1),
|
||||||
(1, 0, 1),
|
(1, 1),
|
||||||
(1, 1, 1),
|
(1, 1),
|
||||||
(2, 0, 1),
|
(2, 1),
|
||||||
(2, 1, 1),
|
(2, 1),
|
||||||
(3, 0, 1),
|
(3, 1),
|
||||||
(3, 1, 1),
|
(3, 1),
|
||||||
(4, 0, 1),
|
(4, 1),
|
||||||
(4, 1, 1),
|
(4, 1),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
new object[]
|
new object[]
|
||||||
{
|
{
|
||||||
0, 1, 10, 1, new (int BlockIndex, int StartingItemIndex, long ItemsCount)[]
|
0, 1, 10, 1, new (int BlockIndex, long ItemsCount)[]
|
||||||
{
|
{
|
||||||
(0, 0, 1),
|
(0, 1),
|
||||||
(1, 0, 1),
|
(1, 1),
|
||||||
(2, 0, 1),
|
(2, 1),
|
||||||
(3, 0, 1),
|
(3, 1),
|
||||||
(4, 0, 1),
|
(4, 1),
|
||||||
(5, 0, 1),
|
(5, 1),
|
||||||
(6, 0, 1),
|
(6, 1),
|
||||||
(7, 0, 1),
|
(7, 1),
|
||||||
(8, 0, 1),
|
(8, 1),
|
||||||
(9, 0, 1),
|
(9, 1),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in new issue
Block a user