diff --git a/Recyclable.Collections/ItemRange.cs b/Recyclable.Collections/ItemRange.cs index 892aa43..37a31fa 100644 --- a/Recyclable.Collections/ItemRange.cs +++ b/Recyclable.Collections/ItemRange.cs @@ -5,7 +5,6 @@ namespace Recyclable.Collections public sealed class ItemRange { public int BlockIndex; - public int StartingItemIndex; public long ItemsToSearchCount; 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; ItemsToSearchCount = itemsToSearchCount; - StartingItemIndex = startingItemIndex; } public void Dispose() diff --git a/Recyclable.Collections/ItemRangesIterator.cs b/Recyclable.Collections/ItemRangesIterator.cs index 74c7d9e..9a150aa 100644 --- a/Recyclable.Collections/ItemRangesIterator.cs +++ b/Recyclable.Collections/ItemRangesIterator.cs @@ -8,31 +8,22 @@ namespace Recyclable.Collections [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.NoInlining)] public static void Iterate(in ParallelSynchronizationContext context, in SearchConfiguration search, Func, ItemRange, bool> action) { - int itemIndex = 0; ItemRange itemRange; int blockIndex = search.StartingBlockIndex; 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)) { break; } - itemsCount -= search.Step; - //blockIndex = (int)Math.DivRem(itemIndex + step, blockSize, out itemIndex); - blockIndex += (int)((itemIndex + search.Step) >> search.BlockSizePow2BitShift); - itemIndex = (int)((itemIndex + search.Step) & search.BlockSizeMinus1); - //itemIndex += step; - //while (itemIndex >= blockSize) - //{ - // blockIndex++; - // itemIndex -= blockSize; - //} + itemsCount -= search.BlockSize; + blockIndex++; } - itemRange = new(blockIndex, itemIndex, itemsCount); + itemRange = new(blockIndex, itemsCount); _ = action(context, search, itemRange); } } diff --git a/Recyclable.Collections/Parallel/ParallelSynchronizationContext.cs b/Recyclable.Collections/Parallel/ParallelSynchronizationContext.cs index e41c3f0..2a2fc31 100644 --- a/Recyclable.Collections/Parallel/ParallelSynchronizationContext.cs +++ b/Recyclable.Collections/Parallel/ParallelSynchronizationContext.cs @@ -2,7 +2,7 @@ namespace Recyclable.Collections.Parallel { public sealed class ParallelSynchronizationContext : IDisposable { - public readonly ManualResetEventSlimmer ItemFoundSignal; + internal bool _isItemFound; public readonly Barrier AllDoneSignal; public long FoundItemIndex; @@ -11,16 +11,21 @@ namespace Recyclable.Collections.Parallel public ParallelSynchronizationContext() { - ItemFoundSignal = ManualResetEventSlimmerPool.Create(false); - AllDoneSignal = new(0); + AllDoneSignal = new(0); } public ParallelSynchronizationContext(int participantCount) { - ItemFoundSignal = ManualResetEventSlimmerPool.Create(false); AllDoneSignal = new(participantCount); } + public void SetItemFound() + { + _isItemFound = true; + } + + public bool IsItemFound => _isItemFound; + public void Dispose() { if (!_returned) @@ -30,7 +35,6 @@ namespace Recyclable.Collections.Parallel } else { - ItemFoundSignal.Dispose(); AllDoneSignal.Dispose(); GC.SuppressFinalize(this); } diff --git a/Recyclable.Collections/Parallel/ParallelSynchronizationContextPool.cs b/Recyclable.Collections/Parallel/ParallelSynchronizationContextPool.cs index d4245bf..aa0142b 100644 --- a/Recyclable.Collections/Parallel/ParallelSynchronizationContextPool.cs +++ b/Recyclable.Collections/Parallel/ParallelSynchronizationContextPool.cs @@ -17,9 +17,9 @@ namespace Recyclable.Collections.Parallel _updateLock.Exit(false); } - if (context.ItemFoundSignal._isSet) + if (context._isItemFound) { - context.ItemFoundSignal.Reset(); + context._isItemFound = false; } switch (context.AllDoneSignal.ParticipantsRemaining) @@ -40,7 +40,7 @@ namespace Recyclable.Collections.Parallel if (context.FoundItemIndex >= 0) { - context.FoundItemIndex = -1L; + context.FoundItemIndex = RecyclableDefaults.ItemNotFoundIndexLong; } context._returned = false; diff --git a/Recyclable.Collections/Pools/ItemRangePool.cs b/Recyclable.Collections/Pools/ItemRangePool.cs index 532988f..6b332fe 100644 --- a/Recyclable.Collections/Pools/ItemRangePool.cs +++ b/Recyclable.Collections/Pools/ItemRangePool.cs @@ -7,7 +7,7 @@ namespace Recyclable.Collections.Pools private static readonly SpinLock _updateLock = new(false); public static readonly ObjectPool Shared = new DefaultObjectPool(new DefaultPooledObjectPolicy()); - public static ItemRange Create(int blockIndex, int startingItemIndex, long itemsToSearchCount) + public static ItemRange Create(int blockIndex, long itemsToSearchCount) { bool lockTaken = false; _updateLock.Enter(ref lockTaken); @@ -20,7 +20,6 @@ namespace Recyclable.Collections.Pools itemRange._returned = false; itemRange.BlockIndex = blockIndex; itemRange.ItemsToSearchCount = itemsToSearchCount; - itemRange.StartingItemIndex = startingItemIndex; return itemRange; } } diff --git a/Recyclable.Collections/RecyclableDefaults.cs b/Recyclable.Collections/RecyclableDefaults.cs index 03154ca..274a911 100644 --- a/Recyclable.Collections/RecyclableDefaults.cs +++ b/Recyclable.Collections/RecyclableDefaults.cs @@ -4,7 +4,10 @@ { public const int BlockSize = 16_384; public const int Capacity = 1; + public const long MinItemsCountForParallelization = 786_432; public const int MinPooledArrayLength = 128; public const int MaxPooledBlockSize = 2_147_483_591; + public const long ItemNotFoundIndexLong = -1L; + public const int ItemNotFoundIndex = -1; } } diff --git a/Recyclable.Collections/RecyclableList.RecyclableListIndexOfHelpers.cs b/Recyclable.Collections/RecyclableList.RecyclableListIndexOfHelpers.cs new file mode 100644 index 0000000..03b00bc --- /dev/null +++ b/Recyclable.Collections/RecyclableList.RecyclableListIndexOfHelpers.cs @@ -0,0 +1,295 @@ +using System.Runtime.CompilerServices; +using Recyclable.Collections.Parallel; + +namespace Recyclable.Collections +{ + public partial class RecyclableList + { + public static class RecyclableListIndexOfHelpers + { + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + // [MethodImpl(MethodImplOptions.AggressiveInlining)] + // [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.NoOptimization)] + private static void ScheduleIndexOfTask(ParallelSynchronizationContext context, RecyclableList list, int blockIndex, T itemToFind) + { + _ = context.AllDoneSignal.AddParticipant(); + // & WAS SLOWER + // _ = ThreadPool.QueueUserWorkItem((_) => + _ = Task.Factory.StartNew(() => + { + try + { + if (context._isItemFound) + { + return; + } + + long index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, list._blockSize); + if (context._isItemFound) + { + return; + } + else if (index >= 0) + { + context._isItemFound = true; + _ = Interlocked.Exchange(ref context.FoundItemIndex, (blockIndex << list._blockSizePow2BitShift) + index); + return; + } + + index = Array.IndexOf(list._memoryBlocks[blockIndex + 1], itemToFind, 0, list._blockSize); + if (context._isItemFound) + { + return; + } + else if (index >= 0) + { + context._isItemFound = true; + _ = Interlocked.Exchange(ref context.FoundItemIndex, ((blockIndex + 1) << list._blockSizePow2BitShift) + index); + return; + } + + index = Array.IndexOf(list._memoryBlocks[blockIndex + 2], itemToFind, 0, list._blockSize); + if (context._isItemFound) + { + return; + } + else if (index >= 0) + { + context._isItemFound = true; + _ = Interlocked.Exchange(ref context.FoundItemIndex, ((blockIndex + 2) << list._blockSizePow2BitShift) + index); + return; + } + + index = Array.IndexOf(list._memoryBlocks[blockIndex + 3], itemToFind, 0, list._blockSize); + if (!context._isItemFound && index >= 0) + { + context._isItemFound = true; + _ = Interlocked.Exchange(ref context.FoundItemIndex, ((blockIndex + 3) << list._blockSizePow2BitShift) + index); + } + + //itemsToSearch -= blockSize; + //if (itemsToSearch > 0) + //{ + // if (itemsToSearch < blockSize) + // { + // blockSize = (int)itemsToSearch; + // } + + // index = Array.IndexOf(list._memoryBlocks[blockIndex + 2], itemToFind, 0, blockSize); + // if (index >= 0) + // { + // if (!context.ItemFoundSignal.IsSet) + // { + // context.ItemFoundSignal.Set(); + // _ = Interlocked.Exchange(ref context.FoundItemIndex, ((blockIndex + 2) << list._blockSizePow2BitShift) + index); + // } + + // return; + // } + //} + + //itemsToSearch -= blockSize; + //if (itemsToSearch > 0) + //{ + // if (itemsToSearch < blockSize) + // { + // blockSize = (int)itemsToSearch; + // } + + // index = Array.IndexOf(list._memoryBlocks[blockIndex + 3], itemToFind, 0, blockSize); + // if (!context.ItemFoundSignal.IsSet && index >= 0) + // { + // context.ItemFoundSignal.Set(); + // _ = Interlocked.Exchange(ref context.FoundItemIndex, ((blockIndex + 3) << list._blockSizePow2BitShift) + index); + + // return; + // } + //} + } + catch (Exception e) + { + e.Data.Add("list", list); + e.Data.Add("itemToFind", itemToFind); + e.Data.Add("blockIndex", blockIndex); + _ = Interlocked.Exchange(ref context.Exception, e); + } + finally + { + context.AllDoneSignal.RemoveParticipant(); + } + }); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + // [MethodImpl(MethodImplOptions.AggressiveInlining)] + // [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.NoOptimization)] + private static void ScheduleIndexOfTaskSingle(ParallelSynchronizationContext context, RecyclableList list, int blockIndex, T itemToFind) + { + _ = context.AllDoneSignal.AddParticipant(); + // & WAS SLOWER + // _ = ThreadPool.QueueUserWorkItem((_) => + _ = Task.Factory.StartNew(() => + { + try + { + if (context._isItemFound) + { + return; + } + + long index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, list._blockSize); + if (context._isItemFound) + { + return; + } + else if (index >= 0) + { + context._isItemFound = true; + _ = Interlocked.Exchange(ref context.FoundItemIndex, (blockIndex << list._blockSizePow2BitShift) + index); + } + } + catch (Exception e) + { + _ = Interlocked.Exchange(ref context.Exception, e); + } + finally + { + context.AllDoneSignal.RemoveParticipant(); + } + }); + } + + private const int BlocksForEachTask = 4; + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + public static long IndexOfParallel(RecyclableList list, in T item) + { + // & WAS SLOWER + //long itemsCount = Array.IndexOf(list._memoryBlocks[0], item, 0, (int)Math.Min(list._blockSize, list._longCount)); + //if (itemsCount >= 0) + //{ + // return itemsCount; + //} + + //using var context = new ParallelSynchronizationContext(1); + ParallelSynchronizationContext context = ParallelSynchronizationContextPool.GetWithOneParticipant(); + // (long)(_longCount * 0.329) => most efficient step, based on benchmarks + //Iterate(this, context, item); + + // TODO: Switch to use _blockSize instead of list._longCountIndeexOfStep for simplifications & better performance. + + // & WAS SLOWER without + int blockIndex = 2; + // & WAS SLOWER + //long itemsCount = list._longCount - list._blockSize; + // & WAS SLOWER + // int blockSize = _blockSize; + // & WAS SLOWER + // while ((blockIndex << list._blockSizePow2BitShift) + itemIndex > step) + //long blockSize = list._blockSize << 1; + int lastBlockWithData = list._lastBlockWithData - BlocksForEachTask; + while (blockIndex < lastBlockWithData) + { + // At this point itemIndex is limited to blockSize range - int. + ScheduleIndexOfTask(context, list, blockIndex, item); + if (context._isItemFound) + { + break; + } + + blockIndex += BlocksForEachTask; + } + + if (!context._isItemFound) + { + while (blockIndex < list._lastBlockWithData) + { + ScheduleIndexOfTaskSingle(context, list, blockIndex, item); + if (context._isItemFound) + { + break; + } + + blockIndex++; + } + } + + int itemIndex = Array.IndexOf(list._memoryBlocks[list._lastBlockWithData], item, 0, list._nextItemIndex > 0 ? list._nextItemIndex : list._blockSize); + if (itemIndex >= 0) + { + context._isItemFound = true; + _ = Interlocked.Exchange(ref context.FoundItemIndex, ((long)blockIndex << list._blockSizePow2BitShift) + itemIndex); + } + + context.AllDoneSignal.SignalAndWait(); + if (context.Exception == null) + { + return context.FoundItemIndex; + } + + context.Exception.CaptureAndRethrow(); + return RecyclableDefaults.ItemNotFoundIndexLong; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + public static long IndexOfSequential(RecyclableList list, in T item) //, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) + { + int itemIndex; + // & WAS SLOWER + //int lastBlockWithData = list._lastBlockWithData; + Span memoryBlocksSpan = new(list._memoryBlocks, 0, list._lastBlockWithData + 1); + int blockIndex; + for (blockIndex = 1; blockIndex < list._lastBlockWithData; blockIndex += 4) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, list._blockSize); + if (itemIndex >= 0) + { + return itemIndex + ((long)blockIndex << list._blockSizePow2BitShift); + } + + if (blockIndex + 1 < list._lastBlockWithData) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex + 1], item, 0, list._blockSize); + if (itemIndex >= 0) + { + return itemIndex + ((long)(blockIndex + 1)<< list._blockSizePow2BitShift); + } + + if (blockIndex + 2 < list._lastBlockWithData) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex + 2], item, 0, list._blockSize); + if (itemIndex >= 0) + { + return itemIndex + ((long)(blockIndex + 2)<< list._blockSizePow2BitShift); + } + + if (blockIndex + 3 < list._lastBlockWithData) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex + 3], item, 0, list._blockSize); + if (itemIndex >= 0) + { + return itemIndex + ((long)(blockIndex + 3)<< list._blockSizePow2BitShift); + } + } + } + } + } + + //if (blockIndex == list._lastBlockWithData) + //{ + itemIndex = list._nextItemIndex != 0 + ? Array.IndexOf(memoryBlocksSpan[list._lastBlockWithData], item, 0, list._nextItemIndex) + : Array.IndexOf(memoryBlocksSpan[list._lastBlockWithData], item, 0, list._blockSize); + //} + //else + //{ + // return RecyclableDefaults.ItemNotFoundIndexLong; + //} + + return itemIndex >= 0 ? itemIndex + ((long)list._lastBlockWithData << list._blockSizePow2BitShift) : RecyclableDefaults.ItemNotFoundIndexLong; + } + + public static int Min(int a, int b, int c) => a < b ? a < c ? a : c : b < c ? b : c; + } + } +} diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index f3c1a24..22713b4 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -2,19 +2,14 @@ using System.Collections; using System.Numerics; using System.Runtime.CompilerServices; -using Recyclable.Collections.Parallel; using Recyclable.Collections.Pools; [assembly: InternalsVisibleTo("Recyclable.Collections.Benchmarks")] namespace Recyclable.Collections { - public class RecyclableList : IDisposable, IList + public partial class RecyclableList : IDisposable, IList { - private const long ItemNotFoundIndexLong = -1L; - private const int ItemNotFoundIndex = -1; - private const double OptimalParallelSearchStep = 0.1;// 0.329; - private static readonly ArrayPool _defaultMemoryBlocksPool = ArrayPool.Create(); private static readonly ArrayPool _defaultBlockArrayPool = ArrayPool.Create(); private static readonly T[][] _emptyMemoryBlocksArray = new T[0][]; @@ -24,7 +19,7 @@ namespace Recyclable.Collections internal byte _blockSizePow2BitShift; internal int _blockSizeMinus1; private int _nextItemBlockIndex; - private int _nextItemIndex; + internal int _nextItemIndex; private int _reservedBlockCount; protected static readonly bool NeedsClearing = !typeof(T).IsValueType; @@ -42,19 +37,15 @@ namespace Recyclable.Collections public int Count => checked((int)_longCount); 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; public long LongCount { get => _longCount; - set - { - _longCount = value; - _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); - } + set => _longCount = value; } public int ReservedBlockCount => _reservedBlockCount; @@ -75,7 +66,7 @@ namespace Recyclable.Collections int targetBlockIndex = (int)(index >> list._blockSizePow2BitShift); int targetItemIndex = (int)(index & list._blockSizeMinus1); - int lastTakenBlockIndex = list.LastBlockWithData; + int lastTakenBlockIndex = list._lastBlockWithData; while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != list._nextItemBlockIndex))) { int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0 @@ -150,6 +141,8 @@ namespace Recyclable.Collections if (!enumerator.MoveNext()) { + _lastBlockWithData = targetBlockIdx; + _longCount += copied + i - _longCount; if (targetItemIdx < blockSize) { _nextItemBlockIndex = targetBlockIdx; @@ -161,8 +154,6 @@ namespace Recyclable.Collections _nextItemIndex = 0; } - _longCount += copied + i - _longCount; - _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); return; } @@ -220,153 +211,9 @@ namespace Recyclable.Collections } _longCount = requiredCapacity; - _longCountIndexOfStep = Math.Max((long)(requiredCapacity * OptimalParallelSearchStep), 1L); _nextItemBlockIndex = targetBlockIdx; _nextItemIndex = targetItemIdx; - } - - // & WAS SLOWER - // [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] - private static long DoLongIndexOfParallel(RecyclableList 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 list, T itemToFind, ItemRange itemRange) - { - _ = context.AllDoneSignal.AddParticipant(); - // & WAS SLOWER - // _ = ThreadPool.QueueUserWorkItem((_) => - _ = 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 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; + _lastBlockWithData = targetBlockIdx - (targetItemIdx > 0 ? 0 : 1); } /// @@ -482,54 +329,32 @@ namespace Recyclable.Collections // [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.NoOptimization)] // & WAS SLOWER // [MethodImpl(MethodImplOptions.NoInlining)] - private void Iterate(in ParallelSynchronizationContext context, in T itemToFind) - { - // TODO: Switch to use _blockSize instead of list._longCountIndeexOfStep for simplifications & better performance. + //private static void Iterate(RecyclableList list, in ParallelSynchronizationContext context, in T itemToFind) + //{ + // // TODO: Switch to use _blockSize instead of list._longCountIndeexOfStep for simplifications & better performance. - // & WAS SLOWER without - ItemRange itemRange; - 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; - } + // // & WAS SLOWER without + // int blockIndex = 0; - 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 - // blockIndex = (int)Math.DivRem(itemIndex + step, step, out itemIndex); - // & WAS SLOWER - // blockIndex += (int)((itemIndex + list._longCountIndexOfStep) >> list._blockSizePow2BitShift); - // itemIndex = (int)((itemIndex + list._longCountIndexOfStep) & list._blockSizeMinus1); + // itemsCount -= list._blockSize; + // blockIndex++; + // } - itemIndex += _longCountIndexOfStep; - 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); - } + // _ = ScheduleIndexOfTask(context, list, blockIndex, (int)itemsCount, itemToFind); + //} [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] protected static void SetupBlockArrayPooling(RecyclableList list, int blockSize, ArrayPool? blockArrayPool = null) @@ -648,9 +473,12 @@ namespace Recyclable.Collections _nextItemBlockIndex++; _nextItemIndex = 0; } + else if (_nextItemIndex == 1) + { + _lastBlockWithData++; + } _longCount++; - _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); } public void AddRange(in T[] items) @@ -693,8 +521,8 @@ namespace Recyclable.Collections _nextItemIndex = itemsSpan.Length; _longCount = targetCapacity; - _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); _nextItemBlockIndex = targetBlockIndex; + _lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1); } public void AddRange(RecyclableArrayList items) @@ -733,8 +561,8 @@ namespace Recyclable.Collections _nextItemIndex = itemsSpan.Length; itemsSpan.CopyTo(targetBlockArraySpan); _longCount = targetCapacity; - _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); _nextItemBlockIndex = targetBlockIndex; + _lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1); } public void AddRange(RecyclableList items) @@ -794,6 +622,7 @@ namespace Recyclable.Collections } } + _lastBlockWithData = targetBlockIndex; if ((targetCapacity & _blockSizeMinus1) == 0) { _nextItemIndex = 0; @@ -806,7 +635,6 @@ namespace Recyclable.Collections } _longCount = targetCapacity; - _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); } public void AddRange(List items) @@ -853,7 +681,7 @@ namespace Recyclable.Collections _nextItemBlockIndex = targetBlockIndex; _longCount = targetCapacity; - _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + _lastBlockWithData = targetBlockIndex - (_nextItemIndex > 0 ? 0 : 1); } public void AddRange(IList items) @@ -898,9 +726,9 @@ namespace Recyclable.Collections itemsSpan.CopyTo(targetBlockArraySpan); _longCount = targetCapacity; - _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); _nextItemBlockIndex = targetBlockIndex; _nextItemIndex = itemsSpan.Length; + _lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1); } finally { @@ -976,8 +804,8 @@ namespace Recyclable.Collections _reservedBlockCount = 0; _nextItemBlockIndex = 0; _nextItemIndex = 0; + _lastBlockWithData = RecyclableDefaults.ItemNotFoundIndex; _longCount = 0; - _longCountIndexOfStep = 1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1010,17 +838,81 @@ namespace Recyclable.Collections public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int IndexOf(T item) => checked((int)DoIndexOfParallel(item)); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + 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(); [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public long LongIndexOf(T item) => _longCount == 0 - ? ItemNotFoundIndexLong - : _longCount <= _blockSize - ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) - : DoIndexOfParallel(item); + public long LongIndexOf(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 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)] public bool Remove(T item) @@ -1029,7 +921,6 @@ namespace Recyclable.Collections if (index >= 0) { _longCount--; - _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); if (index < _longCount) { @@ -1039,6 +930,10 @@ namespace Recyclable.Collections if (_nextItemIndex > 0) { _nextItemIndex--; + if (_nextItemIndex == 0) + { + _lastBlockWithData--; + } } else { @@ -1080,6 +975,8 @@ namespace Recyclable.Collections { _nextItemBlockIndex--; } + + _lastBlockWithData--; } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] @@ -1091,7 +988,6 @@ namespace Recyclable.Collections } _longCount--; - _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); if (index < _longCount) { @@ -1101,6 +997,10 @@ namespace Recyclable.Collections if (_nextItemIndex > 0) { _nextItemIndex--; + if (_nextItemIndex == 0) + { + _lastBlockWithData--; + } } else { @@ -1125,7 +1025,6 @@ namespace Recyclable.Collections } _longCount--; - _longCountIndexOfStep = Math.Max((long)Math.Floor(_longCount * OptimalParallelSearchStep), 1L); if (index < _longCount) { @@ -1135,6 +1034,10 @@ namespace Recyclable.Collections if (_nextItemIndex > 0) { _nextItemIndex--; + if (_nextItemIndex == 0) + { + _lastBlockWithData--; + } } else { diff --git a/Recyclable.Collections/SearchConfiguration.cs b/Recyclable.Collections/SearchConfiguration.cs index 35c8a2c..622ae2a 100644 --- a/Recyclable.Collections/SearchConfiguration.cs +++ b/Recyclable.Collections/SearchConfiguration.cs @@ -8,7 +8,7 @@ namespace Recyclable.Collections public byte BlockSizePow2BitShift { get; init; } public long ItemsCount { get; init; } public int StartingBlockIndex { get; init; } - public long Step { get; init; } + public long BlockSize { get; init; } // public SearchConfiguration(TList list, in TItem itemToFind) // { @@ -23,7 +23,7 @@ namespace Recyclable.Collections BlockSizeMinus1 = list._blockSizeMinus1, BlockSizePow2BitShift = list._blockSizePow2BitShift, ItemsCount = list._longCount, - Step = list._longCountIndexOfStep + BlockSize = list._blockSize }; } } \ No newline at end of file diff --git a/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs b/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs index 586fbae..57b4973 100644 --- a/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs +++ b/Recyclable.CollectionsTests/ItemRangesIteratorTests.cs @@ -9,7 +9,7 @@ namespace Recyclable.CollectionsTests { [Theory] [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 var mockList = new RecyclableList(); @@ -23,15 +23,15 @@ namespace Recyclable.CollectionsTests BlockSizeMinus1 = blockSize - 1, BlockSizePow2BitShift = blockSizePow2BitShift, 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 ItemRangesIterator.Iterate(synchronizationContext, search, (context, search, itemRange) => { - actualItemRanges.Add((itemRange.BlockIndex, itemRange.StartingItemIndex, itemRange.ItemsToSearchCount)); + actualItemRanges.Add((itemRange.BlockIndex, itemRange.ItemsToSearchCount)); return true; }); @@ -43,102 +43,102 @@ namespace Recyclable.CollectionsTests { 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, 109), - (1, 90, 109), - (2, 71, 6) + (0, 109), + (0, 109), + (1, 109), + (2, 6) } }, 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), - (1, 0, 2), - (2, 0, 2), - (3, 0, 2), - (4, 0, 2), + (0, 2), + (1, 2), + (2, 2), + (3, 2), + (4, 2), } }, 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), - (1, 1, 3), - (3, 0, 3), - (4, 1, 1), + (0, 3), + (1, 3), + (3, 3), + (4, 1), } }, 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), - (3, 0, 3), - (6, 0, 3), - (9, 0, 1), + (0, 3), + (3, 3), + (6, 3), + (9, 1), } }, 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), - (2, 0, 4), - (4, 0, 2), + (0, 4), + (2, 4), + (4, 2), } }, 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), - (2, 0, 2), - (4, 0, 2), - (6, 0, 2), - (8, 0, 2), + (0, 2), + (2, 2), + (4, 2), + (6, 2), + (8, 2), } }, 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, 1), - (1, 0, 1), - (1, 1, 1), - (2, 0, 1), - (2, 1, 1), - (3, 0, 1), - (3, 1, 1), - (4, 0, 1), - (4, 1, 1), + (0, 1), + (0, 1), + (1, 1), + (1, 1), + (2, 1), + (2, 1), + (3, 1), + (3, 1), + (4, 1), + (4, 1), } }, 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), - (1, 0, 1), - (2, 0, 1), - (3, 0, 1), - (4, 0, 1), - (5, 0, 1), - (6, 0, 1), - (7, 0, 1), - (8, 0, 1), - (9, 0, 1), + (0, 1), + (1, 1), + (2, 1), + (3, 1), + (4, 1), + (5, 1), + (6, 1), + (7, 1), + (8, 1), + (9, 1), } }, };