Dirty RecyclableList.IndexOf parallelization implemented.

This commit is contained in:
mlemanczyk committed 2023-04-25 01:51:22 +02:00
1 parent 1bdf0404e4
commit aa78a2ba40
5 files changed
+566 -8

No files matched your search

@@ -0,0 +1,12 @@
using System.Runtime.ExceptionServices;
namespace Recyclable.Collections
{
public static class ExceptionExtensions
{
public static void CaptureAndRethrow(this Exception ex)
{
ExceptionDispatchInfo.Capture(ex).Throw();
}
}
}
@@ -0,0 +1,247 @@
using Microsoft.Extensions.ObjectPool;
using System.Runtime.CompilerServices;
namespace Recyclable.Collections
{
public sealed class ItemRange : IDisposable
{
private static readonly ObjectPool<ItemRange> _defaultPool = ItemRangePool.Shared;
public int BlockIndex;
public int StartingItemIndex;
public long ItemsToSearchCount;
private ObjectPool<ItemRange>? _assignedPool;
public ItemRange()
{
}
public ItemRange(int blockIndex, int startingItemIndex, long itemsToSearchCount)
{
BlockIndex = blockIndex;
ItemsToSearchCount = itemsToSearchCount;
StartingItemIndex = startingItemIndex;
}
public void Dispose()
{
if (_assignedPool != null)
{
var assignedPool = _assignedPool;
_assignedPool = null;
assignedPool.Return(this);
}
GC.SuppressFinalize(this);
}
public static ItemRange Create(int blockIndex, int startingItemIndex, long itemsToSearchCount)
{
var itemRange = _defaultPool.Get();
itemRange.BlockIndex = blockIndex;
itemRange.ItemsToSearchCount = itemsToSearchCount;
itemRange.StartingItemIndex = startingItemIndex;
return itemRange;
}
}
public static class ItemRangePool
{
[ThreadStatic]
private static ObjectPool<ItemRange> _shared;
public static ObjectPool<ItemRange> Shared => _shared;
static ItemRangePool()
{
_shared = new DefaultObjectPool<ItemRange>(new DefaultPooledObjectPolicy<ItemRange>());
}
}
public sealed class BarrierSlimmer : IDisposable
{
private static readonly ObjectPool<BarrierSlimmer> _defaultPool = new DefaultObjectPool<BarrierSlimmer>(new DefaultPooledObjectPolicy<BarrierSlimmer>());
private ObjectPool<BarrierSlimmer>? _assignedPool;
private readonly object _lock = new();
public bool HasParticipants;
public int Participants;
public void AddParticipant()
{
Monitor.Enter(_lock);
if (++Participants == 1)
{
HasParticipants = true;
}
Monitor.Exit(_lock);
}
public void RemoveParticipant()
{
Monitor.Enter(_lock);
if (Participants == 0)
{
Monitor.Exit(_lock);
ThrowNoMoreParticipants();
}
if (--Participants == 0)
{
HasParticipants = false;
}
Monitor.Exit(_lock);
}
private static void ThrowNoMoreParticipants()
{
throw new InvalidOperationException("There are no more participants. The operation is not allowed");
}
public BarrierSlimmer()
{
}
public BarrierSlimmer(int participants)
{
Participants = participants;
HasParticipants = participants > 0;
}
public static BarrierSlimmer Create(int participants)
{
var result = _defaultPool.Get();
result.Participants = participants;
result.HasParticipants = participants > 0;
return result;
}
public void Dispose()
{
var assignedPool = _assignedPool;
_assignedPool = null;
assignedPool?.Return(this);
GC.SuppressFinalize(this);
}
public void SignalAndWait()
{
RemoveParticipant();
if (!HasParticipants)
{
return;
}
if (!HasParticipants)
{
return;
}
if (!HasParticipants)
{
return;
}
var waitSpinner = new SpinWait();
while (HasParticipants)
{
waitSpinner.SpinOnce();
}
}
}
public sealed class ManualResetEventSlimmer : IDisposable
{
private static readonly ObjectPool<ManualResetEventSlimmer> _defaultPool = ManualResetEventSlimmerPool.Shared;
private bool _isSet;
public bool IsSet => _isSet;
private ObjectPool<ManualResetEventSlimmer>? _assignedPool;
public ManualResetEventSlimmer(bool initialState)
{
_isSet = initialState;
}
public ManualResetEventSlimmer()
{
}
public void Set()
{
_isSet = true;
}
public void Reset()
{
_isSet = false;
}
public void Dispose()
{
var assignedPool = _assignedPool;
_assignedPool = null;
assignedPool?.Return(this);
GC.SuppressFinalize(this);
}
public static ManualResetEventSlimmer Create(bool initialState)
{
ManualResetEventSlimmer result = _defaultPool.Get();
result._assignedPool = _defaultPool;
result._isSet = initialState;
return result;
}
}
public static class ManualResetEventSlimmerPool
{
[ThreadStatic]
private static ObjectPool<ManualResetEventSlimmer> _shared;
public static ObjectPool<ManualResetEventSlimmer> Shared => _shared;
static ManualResetEventSlimmerPool()
{
_shared = new DefaultObjectPool<ManualResetEventSlimmer>(new DefaultPooledObjectPolicy<ManualResetEventSlimmer>());
}
}
public static class ItemRangesIterator
{
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.NoInlining)]
public static void Iterate(int blockIndex, int blockSize, byte blockSizePow2BitShift, long itemsCount, long step, Func<ItemRange, bool> action)
{
int itemIndex = 0;
ItemRange itemRange;
while (itemsCount > step)
{
itemRange = ItemRange.Create(blockIndex, itemIndex, step);
if (!action(itemRange))
{
break;
}
itemsCount -= step;
//blockIndex = (int)Math.DivRem(itemIndex + step, blockSize, out itemIndex);
blockIndex += (int)((itemIndex + step) >> blockSizePow2BitShift);
itemIndex = (int)((itemIndex + step) & (blockSize - 1));
//itemIndex += step;
//while (itemIndex >= blockSize)
//{
// blockIndex++;
// itemIndex -= blockSize;
//}
}
itemRange = ItemRange.Create(blockIndex, itemIndex, itemsCount);
_ = action(itemRange);
}
}
}
@@ -33,6 +33,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="7.0.5" />
<PackageReference Include="morelinq" Version="3.3.2" />
</ItemGroup>
</Project>
+173 -8
View File
@@ -8,6 +8,7 @@ namespace Recyclable.Collections
public class RecyclableList<T> : IDisposable, IList<T>
{
private const int ItemNotFoundIndex = -1;
private const double OptimalParallelSearchStep = 0.329;
private static readonly ArrayPool<T[]> _defaultMemoryBlocksPool = ArrayPool<T[]>.Create();
private static readonly ArrayPool<T> _defaultBlockArrayPool = ArrayPool<T>.Create();
@@ -38,12 +39,17 @@ namespace Recyclable.Collections
public bool IsReadOnly { get; }
public int LastBlockWithData => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1);
protected long _longCountIndexOfStep;
protected long _longCount;
public long LongCount
{
get => _longCount;
set => _longCount = value;
set
{
_longCount = value;
_longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L);
}
}
public int ReservedBlockCount => _reservedBlockCount;
@@ -152,6 +158,7 @@ namespace Recyclable.Collections
}
_longCount += copied + i - _longCount;
_longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L);
return;
}
@@ -209,11 +216,156 @@ namespace Recyclable.Collections
}
_longCount = requiredCapacity;
_longCountIndexOfStep = Math.Max((long)(requiredCapacity * OptimalParallelSearchStep), 1L);
_nextItemBlockIndex = targetBlockIdx;
_nextItemIndex = targetItemIdx;
return;
}
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
private static long DoLongIndexOfParallel(in RecyclableList<T> list, in ItemRange itemRange, in T itemToFind, in ManualResetEventSlimmer itemFoundSignal)
{
int blockSize = list._blockSize;
int blockIndex = itemRange.BlockIndex;
long itemsToSearchCount = itemRange.ItemsToSearchCount;
int startingItemIndex = itemRange.StartingItemIndex;
T[][] memoryBlocks = list._memoryBlocks;
int index = Array.IndexOf(memoryBlocks[blockIndex], itemToFind, startingItemIndex, (int)Math.Min(blockSize - startingItemIndex, itemsToSearchCount));
if (itemFoundSignal.IsSet)
{
return ItemNotFoundIndex;
}
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 - startingItemIndex;
if (itemsToSearchCount <= 0)
{
return ItemNotFoundIndex;
}
//T itemToFind = itemRange.ItemToFind;
//int lastBlockWithData = itemRange.LastBlockWithData;
blockIndex++;
while (itemsToSearchCount > blockSize)
{
index = Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, blockSize);
if (itemFoundSignal.IsSet)
{
return ItemNotFoundIndex;
}
else if (index >= 0)
{
return ((long)blockIndex << list._blockSizePow2BitShift) + index;
}
itemsToSearchCount -= blockSize;
blockIndex++;
}
index = itemsToSearchCount <= 0 || blockIndex > list.LastBlockWithData
? -1
: blockIndex == list._nextItemBlockIndex
? Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount)
: Array.IndexOf(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 index >= 0 ? ((long)blockIndex << list._blockSizePow2BitShift) + index : ItemNotFoundIndex;
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private long DoIndexOfParallel(T item)
{
using var itemFoundSignal = ManualResetEventSlimmer.Create(false);
using var allDoneSignal = new Barrier(1);
long foundItemIndex = -1L;
Exception? ex = null;
// (long)(_longCount * 0.329) => most efficient step, based on benchmarks
ItemRangesIterator.Iterate(0, _blockSize, _blockSizePow2BitShift, _longCount, _longCountIndexOfStep, LocalIndexOf);
allDoneSignal.SignalAndWait();
if (ex == null)
{
return foundItemIndex;
}
ex.CaptureAndRethrow();
return ItemNotFoundIndex;
//int blockIndex = 0;
//int itemIndex = 0;
//long itemsCount = _longCount;
//while (itemsCount > step)
//{
// if (!LocalIndexOf(new(blockIndex, itemIndex, step)))
// {
// break;
// }
// itemsCount -= step;
// //blockIndex = (int)Math.DivRem(itemIndex + step, blockSize, out itemIndex);
// blockIndex += (int)((itemIndex + step) >> _blockSizePow2BitShift);
// itemIndex = (int)((itemIndex + step) & _blockSizeMinus1);
// //itemIndex += step;
// //while (itemIndex >= blockSize)
// //{
// // blockIndex++;
// // itemIndex -= blockSize;
// //}
//}
//_ = LocalIndexOf(new(blockIndex, itemIndex, itemsCount));
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
bool LocalIndexOf(ItemRange itemRange)
{
_ = allDoneSignal.AddParticipant();
_ = Task.Factory.StartNew(() =>
{
try
{
var index = DoLongIndexOfParallel(this, itemRange, item, itemFoundSignal);
if (index >= 0)
{
itemFoundSignal.Set();
foundItemIndex = index;
}
}
catch (Exception e)
{
//_ = Interlocked.Exchange(ref ex, e);
lock (allDoneSignal)
{
ex = e;
}
}
finally
{
itemRange.Dispose();
allDoneSignal.RemoveParticipant();
}
});
//.ContinueWith(x =>
//{
// if (x.Exception != null)
// {
// lock (allDoneSignal)
// {
// ex = x.Exception;
// }
// }
//});
return !itemFoundSignal.IsSet;
}
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex)
{
@@ -447,7 +599,7 @@ namespace Recyclable.Collections
set => new Span<T>(_memoryBlocks[index >> _blockSizePow2BitShift])[index & _blockSizeMinus1] = value;
}
public T[][] MemoryBlocks { get => _memoryBlocks; }
public T[][] AsArray { get => _memoryBlocks; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(T item)
@@ -465,6 +617,7 @@ namespace Recyclable.Collections
}
_longCount++;
_longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L);
}
public void AddRange(in T[] items)
@@ -507,6 +660,7 @@ namespace Recyclable.Collections
_nextItemIndex = itemsSpan.Length;
_longCount = targetCapacity;
_longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L);
_nextItemBlockIndex = targetBlockIndex;
}
@@ -546,6 +700,7 @@ namespace Recyclable.Collections
_nextItemIndex = itemsSpan.Length;
itemsSpan.CopyTo(targetBlockArraySpan);
_longCount = targetCapacity;
_longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L);
_nextItemBlockIndex = targetBlockIndex;
}
@@ -619,6 +774,7 @@ namespace Recyclable.Collections
}
_longCount = targetCapacity;
_longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L);
}
public void AddRange(List<T> items)
@@ -665,6 +821,7 @@ namespace Recyclable.Collections
_nextItemBlockIndex = targetBlockIndex;
_longCount = targetCapacity;
_longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L);
}
public void AddRange(IList<T> items)
@@ -709,6 +866,7 @@ namespace Recyclable.Collections
itemsSpan.CopyTo(targetBlockArraySpan);
_longCount = targetCapacity;
_longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L);
_nextItemBlockIndex = targetBlockIndex;
_nextItemIndex = itemsSpan.Length;
}
@@ -787,6 +945,7 @@ namespace Recyclable.Collections
_nextItemBlockIndex = 0;
_nextItemIndex = 0;
_longCount = 0;
_longCountIndexOfStep = 1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -822,18 +981,18 @@ namespace Recyclable.Collections
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int IndexOf(T item) => _longCount == 0
? ItemNotFoundIndex
: _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0)
? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount))
: checked((int)DoIndexOf(item, _memoryBlocks, LastBlockWithData, _blockSize, _nextItemIndex));
: _longCount <= _blockSize
? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount))
: checked((int)DoIndexOfParallel(item));
public void Insert(int index, T item) => throw new NotSupportedException();
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public long LongIndexOf(T item) => _longCount == 0
? ItemNotFoundIndex
: _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0)
? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount))
: DoIndexOf(item, _memoryBlocks, LastBlockWithData, _blockSize, _nextItemIndex);
: _longCount <= _blockSize
? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount))
: DoIndexOfParallel(item);
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public bool Remove(T item)
@@ -842,6 +1001,8 @@ namespace Recyclable.Collections
if (index >= 0)
{
_longCount--;
_longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L);
if (index < _longCount)
{
CopyItems(this, index);
@@ -902,6 +1063,8 @@ namespace Recyclable.Collections
}
_longCount--;
_longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L);
if (index < _longCount)
{
CopyItems(this, index);
@@ -934,6 +1097,8 @@ namespace Recyclable.Collections
}
_longCount--;
_longCountIndexOfStep = Math.Max((long)Math.Floor(_longCount * OptimalParallelSearchStep), 1L);
if (index < _longCount)
{
CopyItems(this, index);