From 29cb16793a145b3bf2e5474c2348160e8368bd67 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Thu, 25 May 2023 19:21:41 +0200 Subject: [PATCH] RecyclableList.IndexOf performance improvements. --- .../Parallel/IndexOfSynchronizationContext.cs | 64 ++++- .../IndexOfSynchronizationContextPool.cs | 6 +- .../RecyclableLongList.IndexOfHelpers.cs | 234 +++++++----------- 3 files changed, 152 insertions(+), 152 deletions(-) diff --git a/Recyclable.Collections/Parallel/IndexOfSynchronizationContext.cs b/Recyclable.Collections/Parallel/IndexOfSynchronizationContext.cs index 91f0c25..4a53190 100644 --- a/Recyclable.Collections/Parallel/IndexOfSynchronizationContext.cs +++ b/Recyclable.Collections/Parallel/IndexOfSynchronizationContext.cs @@ -1,22 +1,71 @@ namespace Recyclable.Collections.Parallel { - public sealed class IndexOfSynchronizationContext : IDisposable + public sealed class IndexOfSynchronizationContext { + private readonly SpinWait _participantsSpinWait = new(); + 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 Exception? Exception; - internal bool _returned; + public int Participants => _participants; public IndexOfSynchronizationContext() { - AllDoneSignal = new(0); } public IndexOfSynchronizationContext(int participantCount) { - AllDoneSignal = new(participantCount); + _participants = participantCount; } public void SetItemFound() @@ -33,11 +82,6 @@ namespace Recyclable.Collections.Parallel _returned = true; IndexOfSynchronizationContextPool.Shared.Return(this); } - else - { - AllDoneSignal.Dispose(); - GC.SuppressFinalize(this); - } } } } \ No newline at end of file diff --git a/Recyclable.Collections/Parallel/IndexOfSynchronizationContextPool.cs b/Recyclable.Collections/Parallel/IndexOfSynchronizationContextPool.cs index 9d7f65b..935b71d 100644 --- a/Recyclable.Collections/Parallel/IndexOfSynchronizationContextPool.cs +++ b/Recyclable.Collections/Parallel/IndexOfSynchronizationContextPool.cs @@ -22,14 +22,14 @@ namespace Recyclable.Collections.Parallel context._isItemFound = false; } - switch (context.AllDoneSignal.ParticipantsRemaining) + switch (context._participants) { case > 1: - context.AllDoneSignal.RemoveParticipants(context.AllDoneSignal.ParticipantsRemaining - 1); + context.RemoveParticipants(context._participants - 1); break; case 0: - _ = context.AllDoneSignal.AddParticipant(); + context.AddParticipant(); break; } diff --git a/Recyclable.Collections/RecyclableLongList.IndexOfHelpers.cs b/Recyclable.Collections/RecyclableLongList.IndexOfHelpers.cs index f702481..d84126d 100644 --- a/Recyclable.Collections/RecyclableLongList.IndexOfHelpers.cs +++ b/Recyclable.Collections/RecyclableLongList.IndexOfHelpers.cs @@ -8,161 +8,102 @@ namespace Recyclable.Collections public static class IndexOfHelpers { [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - // [MethodImpl(MethodImplOptions.AggressiveInlining)] - // [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.NoOptimization)] - private static void ScheduleIndexOfTask(IndexOfSynchronizationContext context, RecyclableLongList list, int blockIndex, T itemToFind) + private static void IndexOfIn4ConsecutiveBlocks(IndexOfSynchronizationContext context, RecyclableLongList list, int blockIndex, T itemToFind) { - _ = context.AllDoneSignal.AddParticipant(); - // & WAS SLOWER - // _ = ThreadPool.QueueUserWorkItem((_) => - _ = Task.Factory.StartNew(() => + try { - try + if (context._isItemFound) { - 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; - // } - //} + return; } - catch (Exception e) + + long index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, list._blockSize); + if (context._isItemFound) { - e.Data.Add("list", list); - e.Data.Add("itemToFind", itemToFind); - e.Data.Add("blockIndex", blockIndex); - _ = Interlocked.Exchange(ref context.Exception, e); + return; } - 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)] - // [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.NoOptimization)] - private static void ScheduleIndexOfTaskSingle(IndexOfSynchronizationContext context, RecyclableLongList list, int blockIndex, T itemToFind) + private static void IndexOfIn1Block(IndexOfSynchronizationContext context, RecyclableLongList list, int blockIndex, T itemToFind) { - _ = context.AllDoneSignal.AddParticipant(); - // & WAS SLOWER - // _ = ThreadPool.QueueUserWorkItem((_) => - _ = Task.Factory.StartNew(() => + try { - 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); - } - } - catch (Exception e) + long index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, list._blockSize); + if (!context._isItemFound && index >= 0) { - _ = Interlocked.Exchange(ref context.Exception, e); + context._isItemFound = true; + _ = Interlocked.Exchange(ref context.FoundItemIndex, (blockIndex << list._blockSizePow2BitShift) + index); } - finally - { - context.AllDoneSignal.RemoveParticipant(); - } - }); + } + catch (Exception e) + { + _ = Interlocked.Exchange(ref context.Exception, e); + } + finally + { + context.RemoveParticipant(); + } } private const int BlocksForEachTask = 4; [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] - public static long IndexOfParallel(RecyclableLongList list, in T item) + public static long IndexOfParallel(RecyclableLongList list, T item) { // & WAS SLOWER //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 //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 @@ -191,7 +130,16 @@ namespace Recyclable.Collections while (blockIndex < lastBlockWithData) { // At this point itemIndex is limited to blockSize range - int. - ScheduleIndexOfTask(context, list, blockIndex, item); + + // & WAS SLOWER + // _ = ThreadPool.QueueUserWorkItem((_) => + // 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) { break; @@ -204,7 +152,15 @@ namespace Recyclable.Collections { while (blockIndex < list._lastBlockWithData) { - ScheduleIndexOfTaskSingle(context, list, blockIndex, item); + // & WAS SLOWER + // _ = ThreadPool.QueueUserWorkItem((_) => + // 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) { break; @@ -224,7 +180,7 @@ namespace Recyclable.Collections } } - context.AllDoneSignal.SignalAndWait(); + context.SignalAndWait(); if (context.Exception == null) { try @@ -245,7 +201,7 @@ namespace Recyclable.Collections { context.Dispose(); } - + return RecyclableDefaults.ItemNotFoundIndexLong; }