RecyclableList.IndexOf performance improvements.
This commit is contained in:
1 parent
c4e633e7d3
commit
29cb16793a
3 files changed
+152
-152
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
|
||||||
@@ -245,7 +201,7 @@ namespace Recyclable.Collections
|
|||||||
{
|
{
|
||||||
context.Dispose();
|
context.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
return RecyclableDefaults.ItemNotFoundIndexLong;
|
return RecyclableDefaults.ItemNotFoundIndexLong;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user