RecyclableList.IndexOf performance improvements.

This commit is contained in:
mlemanczyk committed 2023-05-25 19:21:41 +02:00
1 parent c4e633e7d3
commit 29cb16793a
3 files changed
+151 -151

No files matched your search

@@ -1,22 +1,71 @@
namespace Recyclable.Collections.Parallel namespace Recyclable.Collections.Parallel
{ {
public sealed class IndexOfSynchronizationContext : IDisposable public sealed class IndexOfSynchronizationContext
{ {
private readonly SpinWait _participantsSpinWait = new();
internal bool _isItemFound; internal bool _isItemFound;
internal int _participants;
internal bool _returned;
public void AddParticipant()
{
_ = Interlocked.Increment(ref _participants);
}
public void AddParticipants(int newParticipants)
{
_ = Interlocked.Add(ref _participants, newParticipants);
}
public void RemoveParticipants(int participantsToRemove)
{
if (_participants < participantsToRemove)
{
ThrowNotEnoughParticipants();
}
_ = Interlocked.Add(ref _participants, -participantsToRemove);
}
public void RemoveParticipant()
{
if (_participants == 0)
{
ThrowNoMoreParticipants();
}
_ = Interlocked.Decrement(ref _participants);
}
public void SignalAndWait()
{
if (_participants == 0)
{
ThrowNoMoreParticipants();
}
_ = Interlocked.Decrement(ref _participants);
while (_participants > 0)
{
_participantsSpinWait.SpinOnce();
}
}
private static void ThrowNotEnoughParticipants() => throw new InvalidOperationException("Participant cannot be removed, because there are not enough participants.");
private static void ThrowNoMoreParticipants() => throw new InvalidOperationException("Participant cannot be removed, because there are no more participants.");
public readonly Barrier AllDoneSignal;
public long FoundItemIndex; public long FoundItemIndex;
public Exception? Exception; public Exception? Exception;
internal bool _returned; public int Participants => _participants;
public IndexOfSynchronizationContext() public IndexOfSynchronizationContext()
{ {
AllDoneSignal = new(0);
} }
public IndexOfSynchronizationContext(int participantCount) public IndexOfSynchronizationContext(int participantCount)
{ {
AllDoneSignal = new(participantCount); _participants = participantCount;
} }
public void SetItemFound() public void SetItemFound()
@@ -33,11 +82,6 @@ namespace Recyclable.Collections.Parallel
_returned = true; _returned = true;
IndexOfSynchronizationContextPool.Shared.Return(this); IndexOfSynchronizationContextPool.Shared.Return(this);
} }
else
{
AllDoneSignal.Dispose();
GC.SuppressFinalize(this);
}
} }
} }
} }
@@ -22,14 +22,14 @@ namespace Recyclable.Collections.Parallel
context._isItemFound = false; context._isItemFound = false;
} }
switch (context.AllDoneSignal.ParticipantsRemaining) switch (context._participants)
{ {
case > 1: case > 1:
context.AllDoneSignal.RemoveParticipants(context.AllDoneSignal.ParticipantsRemaining - 1); context.RemoveParticipants(context._participants - 1);
break; break;
case 0: case 0:
_ = context.AllDoneSignal.AddParticipant(); context.AddParticipant();
break; break;
} }
@@ -8,161 +8,102 @@ namespace Recyclable.Collections
public static class IndexOfHelpers public static class IndexOfHelpers
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
// [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void IndexOfIn4ConsecutiveBlocks(IndexOfSynchronizationContext context, RecyclableLongList<T> list, int blockIndex, T itemToFind)
// [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.NoOptimization)]
private static void ScheduleIndexOfTask(IndexOfSynchronizationContext context, RecyclableLongList<T> list, int blockIndex, T itemToFind)
{ {
_ = context.AllDoneSignal.AddParticipant(); try
// & WAS SLOWER
// _ = ThreadPool.QueueUserWorkItem<object?>((_) =>
_ = Task.Factory.StartNew(() =>
{ {
try if (context._isItemFound)
{ {
if (context._isItemFound) return;
{
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)
long index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, list._blockSize);
if (context._isItemFound)
{ {
e.Data.Add("list", list); return;
e.Data.Add("itemToFind", itemToFind);
e.Data.Add("blockIndex", blockIndex);
_ = Interlocked.Exchange(ref context.Exception, e);
} }
finally else if (index >= 0)
{ {
context.AllDoneSignal.RemoveParticipant(); 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);
}
}
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.RemoveParticipant();
}
} }
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
// [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void IndexOfIn1Block(IndexOfSynchronizationContext context, RecyclableLongList<T> list, int blockIndex, T itemToFind)
// [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.NoOptimization)]
private static void ScheduleIndexOfTaskSingle(IndexOfSynchronizationContext context, RecyclableLongList<T> list, int blockIndex, T itemToFind)
{ {
_ = context.AllDoneSignal.AddParticipant(); try
// & WAS SLOWER
// _ = ThreadPool.QueueUserWorkItem<object?>((_) =>
_ = Task.Factory.StartNew(() =>
{ {
try if (context._isItemFound)
{ {
if (context._isItemFound) return;
{ }
return;
}
long index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, list._blockSize); long index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, list._blockSize);
if (context._isItemFound) if (!context._isItemFound && index >= 0)
{
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); context._isItemFound = true;
_ = Interlocked.Exchange(ref context.FoundItemIndex, (blockIndex << list._blockSizePow2BitShift) + index);
} }
finally }
{ catch (Exception e)
context.AllDoneSignal.RemoveParticipant(); {
} _ = Interlocked.Exchange(ref context.Exception, e);
}); }
finally
{
context.RemoveParticipant();
}
} }
private const int BlocksForEachTask = 4; private const int BlocksForEachTask = 4;
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
public static long IndexOfParallel(RecyclableLongList<T> list, in T item) public static long IndexOfParallel(RecyclableLongList<T> list, T item)
{ {
// & WAS SLOWER // & WAS SLOWER
//long itemsCount = Array.IndexOf(list._memoryBlocks[0], item, 0, (int)Math.Min(list._blockSize, list._longCount)); //long itemsCount = Array.IndexOf(list._memoryBlocks[0], item, 0, (int)Math.Min(list._blockSize, list._longCount));
@@ -176,8 +117,6 @@ namespace Recyclable.Collections
// (long)(_longCount * 0.329) => most efficient step, based on benchmarks // (long)(_longCount * 0.329) => most efficient step, based on benchmarks
//Iterate(this, context, item); //Iterate(this, context, item);
// TODO: Switch to use _blockSize instead of list._longCountIndeexOfStep for simplifications & better performance.
// & WAS SLOWER without // & WAS SLOWER without
int blockIndex = 2; int blockIndex = 2;
// & WAS SLOWER // & WAS SLOWER
@@ -191,7 +130,16 @@ namespace Recyclable.Collections
while (blockIndex < lastBlockWithData) while (blockIndex < lastBlockWithData)
{ {
// At this point itemIndex is limited to blockSize range - int. // At this point itemIndex is limited to blockSize range - int.
ScheduleIndexOfTask(context, list, blockIndex, item);
// & WAS SLOWER
// _ = ThreadPool.QueueUserWorkItem<object?>((_) =>
// Task.Factory.StartNew(() =>
context.AddParticipant();
var startingBlock = blockIndex;
Task.Run(() => IndexOfIn4ConsecutiveBlocks(context, list, startingBlock, item));
// & WAS SLOWER
// ScheduleIndexOfTask(context, list, blockIndex, item);
if (context._isItemFound) if (context._isItemFound)
{ {
break; break;
@@ -204,7 +152,15 @@ namespace Recyclable.Collections
{ {
while (blockIndex < list._lastBlockWithData) while (blockIndex < list._lastBlockWithData)
{ {
ScheduleIndexOfTaskSingle(context, list, blockIndex, item); // & WAS SLOWER
// _ = ThreadPool.QueueUserWorkItem<object?>((_) =>
// Task.Factory.StartNew(() =>
var startingBlock = blockIndex;
context.AddParticipant();
Task.Run(() => IndexOfIn1Block(context, list, startingBlock, item));
// & WAS SLOWER
// ScheduleIndexOfTaskSingle(context, list, blockIndex, item);
if (context._isItemFound) if (context._isItemFound)
{ {
break; break;
@@ -224,7 +180,7 @@ namespace Recyclable.Collections
} }
} }
context.AllDoneSignal.SignalAndWait(); context.SignalAndWait();
if (context.Exception == null) if (context.Exception == null)
{ {
try try