diff --git a/Recyclable.Collections.Benchmarks/POC/RoundBenchmarks.cs b/Recyclable.Collections.Benchmarks/POC/RoundBenchmarks.cs new file mode 100644 index 0000000..48e1bcf --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/RoundBenchmarks.cs @@ -0,0 +1,27 @@ +using BenchmarkDotNet.Attributes; + +namespace Recyclable.Collections.Benchmarks.POC +{ + public class RoundBenchmarks : PocBenchmarkBase + { + private const double OptimalParallelSearchStep = 0.329; + + [Benchmark(Baseline = true)] + public void RoundWithMathFloor() + { + DoNothing((long)Math.Floor(TestObjectCount * OptimalParallelSearchStep)); + } + + [Benchmark] + public void RoundWithMathRound() + { + DoNothing((long)Math.Round(TestObjectCount * OptimalParallelSearchStep, 0)); + } + + [Benchmark(Baseline = false)] + public void RoundWithTypeCasting() + { + DoNothing((long)(TestObjectCount * OptimalParallelSearchStep)); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/POC/SpanVsArrayBenchmarks.cs b/Recyclable.Collections.Benchmarks/POC/SpanVsArrayBenchmarks.cs new file mode 100644 index 0000000..d4dd096 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/SpanVsArrayBenchmarks.cs @@ -0,0 +1,71 @@ +using BenchmarkDotNet.Attributes; + +namespace Recyclable.Collections.Benchmarks.POC +{ + public class SpanVsArrayBenchmarks : BenchmarkBase + { + //[Benchmark] + public void ArrayGetWithVar() + { + var data = TestObjects; + var dataCount = TestObjectCount; + for (long i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + //[Benchmark(Baseline = true)] + public void ArrayGetWithField() + { + var dataCount = TestObjectCount; + for (long i = 0; i < dataCount; i++) + { + DoNothing(_testObjects![i]); + } + } + + //[Benchmark] + public void SpanGet() + { + var dataCount = TestObjectCount; + Span data = new(_testObjects, 0, dataCount); + for (int i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + [Benchmark] + public void ArraySetWithVar() + { + var data = TestObjects; + var dataCount = TestObjectCount; + for (long i = 0; i < dataCount; i++) + { + data[i] = i; + } + } + + [Benchmark(Baseline = true)] + public void ArraySetWithField() + { + var dataCount = TestObjectCount; + for (long i = 0; i < dataCount; i++) + { + _testObjects![i] = i; + } + } + + [Benchmark] + public void SpanSet() + { + var dataCount = TestObjectCount; + Span data = new(_testObjects, 0, dataCount); + for (int i = 0; i < dataCount; i++) + { + data[i] = i; + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs b/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs new file mode 100644 index 0000000..5e474ec --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs @@ -0,0 +1,280 @@ +using BenchmarkDotNet.Attributes; +using MoreLinq; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace Recyclable.Collections.Benchmarks.POC +{ + [MemoryDiagnoser] + public class WhenParallelSearchBenchmarks : PocBenchmarkBase + { + [MethodImpl(MethodImplOptions.NoInlining)] + private static void DoNothing(T index) + { + //Console.WriteLine(index); + } + + //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 32)] + //public int NumberOfThreads { get; set; } = 3; + + //[Params(10_000, 20_000, 35_000, 50_000, 75_000, 100_000, 125_000, 150_000, 175_000, 200_000, 225_000, 250_000, 275_000, 300_000, 325_000, 350_000, 375_000, 400_000, 500_000)] + //[Params(320_000, 321_000, 322_000, 323_000, 324_000, 325_000, 326_000, 327_000, 328_000, 329_000, 330_000)] + //[Params(329_000, 512_000, 750_000, 1_000_000, 3_290_000)] + //public int Step { get; set; } = 329_000; // This was the fastest so far + + private static readonly int _numberOfProcessors = Environment.ProcessorCount; + private RecyclableList? _testData; + + public object? ItemToFind { get; set; } + + [GlobalSetup] + public void Setup() + { + _testData = Enumerable.Range(1, TestObjectCount).Cast().ToRecyclableList(BlockSize); + ItemToFind = _testData.Last(); + } + + [GlobalCleanup] + public void Cleanup() + { + _testData = default; + } + + + [Benchmark(Baseline = true)] + public void IndexOfSequentially() + { + long index = DoLongIndexOfSequentially(); + if (index >= 0) + { + DoNothing(index); + } + else + { + throw new Exception("Item not found"); + } + } + + private long DoLongIndexOfSequentially() + { + // This won't work for TestObjectCount == 0 + int index; + int blockSize = _testData!.BlockSize; + int lastBlockWithData = _testData.LastBlockWithData; + var memoryBlocks = _testData.AsArray; + var itemToFind = ItemToFind; + int blockIndex = 0; + while (true) + { + index = Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, blockSize); + if (index >= 0) + { + return (blockIndex << _testData.BlockSizePow2BitShift) + index; + } + + if (blockIndex + 1 < lastBlockWithData) + { + blockIndex++; + } + else + { + break; + } + } + + index = lastBlockWithData == _testData.NextItemBlockIndex + ? Array.IndexOf(memoryBlocks[lastBlockWithData], itemToFind, 0, _testData.NextItemIndex) + : Array.IndexOf(memoryBlocks[lastBlockWithData], itemToFind, 0, blockSize); + + return index >= 0 ? (lastBlockWithData << _testData.BlockSizePow2BitShift) + index : -1; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static long DoLongIndexOfParallel(RecyclableList list, in long itemToFind, ItemRange searchInfo, ManualResetEventSlim itemFoundSignal) + { + int index; + int blockSize = list.BlockSize; + int blockIndex = searchInfo.BlockIndex; + long itemsToSearchCount = searchInfo.ItemsToSearchCount; + long[][] memoryBlocks = list.AsArray; + index = Array.IndexOf(memoryBlocks[searchInfo.BlockIndex], itemToFind, searchInfo.StartingItemIndex, blockSize - searchInfo.StartingItemIndex); + if (itemFoundSignal.IsSet) + { + return -1; + } + else if (index >= 0) + { + return (blockIndex << list.BlockSizePow2BitShift) + index; + } + + itemsToSearchCount -= blockSize - searchInfo.StartingItemIndex; + if (itemsToSearchCount < 0) + { + return -1; + } + + int lastBlockWithData = blockIndex + (int)(itemsToSearchCount >> list.BlockSizePow2BitShift) + ((itemsToSearchCount & (list.BlockSize - 1)) > 0 ? 1 : 0); + blockIndex++; + while (true) + { + index = Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, blockSize); + if (itemFoundSignal.IsSet) + { + return -1; + } + else if (index >= 0) + { + return ((long)blockIndex << list.BlockSizePow2BitShift) + index; + } + + itemsToSearchCount -= blockSize; + if (blockIndex + 1 < lastBlockWithData && itemsToSearchCount < 0) + { + blockIndex++; + } + else + { + break; + } + } + + index = itemsToSearchCount < 0 || itemFoundSignal.IsSet || lastBlockWithData > list.NextItemBlockIndex + ? -1 + : lastBlockWithData == list.NextItemBlockIndex + ? Array.IndexOf(memoryBlocks[lastBlockWithData], itemToFind, 0, checked((int)Math.Min(list.NextItemIndex, itemsToSearchCount))) + : Array.IndexOf(memoryBlocks[lastBlockWithData], itemToFind, 0, checked((int)Math.Min(blockSize, itemsToSearchCount))); + + return itemFoundSignal.IsSet || index < 0 ? -1 : ((long)lastBlockWithData << list.BlockSizePow2BitShift) + index; + } + + [Benchmark] + public void IndexOfParallel() + { + var index = DoIndexOfParallel(); + if (index >= 0) + { + DoNothing(index); + } + else + { + throw new Exception("Item not found"); + } + } + + private long DoIndexOfParallel() + { + using var itemFoundSignal = new ManualResetEventSlim(false); + using var allDoneSignal = new Barrier(1); + //long step = Step;// TestObjectCount / NumberOfThreads; + int blockSize = _testData!.BlockSize; + //object[][] memoryBlocks = _testData.AsArray(); + //long itemsRemaining = testObjectCount; + var foundItemIndex = -1L; + var itemToFind = ItemToFind; + //ItemRangesIterator.Iterate(0, blockSize, BlockSizePow2BitShift, TestObjectCount, (long)(TestObjectCount * 0.329), (searchInfo) => + //{ + // _ = allDoneSignal.AddParticipant(); + // _ = Task.Factory.StartNew((state) => + // { + // try + // { + // var index = DoLongIndexOfParallel(_testData, itemToFind, searchInfo, itemFoundSignal); + // if (index >= 0) + // { + // itemFoundSignal.Set(); + // lock (itemFoundSignal) + // { + // foundItemIndex = index; + // } + // } + // } + // finally + // { + // lock (allDoneSignal) + // { + // allDoneSignal.RemoveParticipant(); + // } + // } + // }, state: searchInfo); + + // return !itemFoundSignal.IsSet; + //}); + + //while (true) + //{ + // var toGive = Math.Min(step, itemsRemaining); + // SearchInfo searchInfo = new( + // blockIndex: blockIndex, + // startingItemIndex: itemIndex, + // itemsToSearchCount: toGive, + // itemToFind: ItemToFind, + // itemFoundSignal: itemFoundSignal); + + // _ = Task.Factory.StartNew((state) => + // { + // try + // { + // var index = DoLongIndexOfParallel(_testData, (SearchInfo)state!); + // if (index >= 0) + // { + // itemFoundSignal.Set(); + // lock (itemFoundSignal) + // { + // foundItemIndex = index; + // } + // } + // } + // finally + // { + // lock (allDoneSignal) + // { + // allDoneSignal.RemoveParticipant(); + // } + // } + // }, state: searchInfo); + + // //itemIndex = checked((int)((itemIndex + toGive) >> BlockSizePow2BitShift)) - 1; + // while (toGive > 0) + // { + // if (itemIndex + toGive < blockSize) + // { + // itemIndex += (int)toGive; + // break; + // } + // else + // { + // blockIndex++; + // //itemIndex += blockSize; + // //if (itemIndex > blockSize) + // //{ + // // itemIndex + // //} + // //if (itemIndex + blockSize < ) + // //itemIndex = (int)(toGive - blockSize + itemIndex); + // toGive -= blockSize; + // } + + // //if (blockIndex == _testData.LastBlockWithData) + // //{ + // // itemIndex = checked((int)toGive); + // // break; + // //} + + // } + + + // if (threadIndex + 1 < numberOfRanges && blockIndex <= _testData.LastBlockWithData) + // { + // threadIndex++; + // } + // else + // { + // break; + // } + //} + + allDoneSignal.SignalAndWait(); + return foundItemIndex; + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index 4bc71a0..43fc561 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -37,12 +37,15 @@ internal class Program static void RunPocBenchmarks() { - _ = BenchmarkRunner.Run(BenchmarkConfig); + _ = BenchmarkRunner.Run(BenchmarkConfig); _ = BenchmarkRunner.Run(BenchmarkConfig); _ = BenchmarkRunner.Run(BenchmarkConfig); + _ = BenchmarkRunner.Run(BenchmarkConfig); _ = BenchmarkRunner.Run(BenchmarkConfig); - _ = BenchmarkRunner.Run(BenchmarkConfig); - _ = BenchmarkRunner.Run(BenchmarkConfig); + _ = BenchmarkRunner.Run(BenchmarkConfig); + _ = BenchmarkRunner.Run(BenchmarkConfig); + _ = BenchmarkRunner.Run(BenchmarkConfig); + //ArraySizeLimitPocBenchmarks>(BenchmarkConfig); } private static void Main(string[] args)