diff --git a/Recyclable.Collections.Benchmarks/BenchmarkBase.cs b/Recyclable.Collections.Benchmarks/BenchmarkBase.cs index 1c2dbe0..874f783 100644 --- a/Recyclable.Collections.Benchmarks/BenchmarkBase.cs +++ b/Recyclable.Collections.Benchmarks/BenchmarkBase.cs @@ -1,24 +1,9 @@ -using BenchmarkDotNet.Attributes; +using Recyclable.Collections.Benchmarks.POC; namespace Recyclable.Collections.Benchmarks { - public class BenchmarkBase + public class BenchmarkBase : PocBenchmarkBase { - //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] - //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] - public long TestObjectCount = 1_000_000; - - //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200)] - //[Params(10, 20, 50, 100)] - //[Params(1, 10)] - public int Divider = 10; - public int BlockSize => TestObjectCount switch - { - 0 => 1, - > 0 and <= 10 => (int)TestObjectCount, - _ => TestObjectCount / Divider > 0 ? checked((int)(TestObjectCount / Divider)) : checked((int)TestObjectCount) - }; - protected object[]? _testObjects; protected object[] TestObjects => _testObjects ?? throw new NullReferenceException("Something is wrong and the field is not initialized"); diff --git a/Recyclable.Collections.Benchmarks/DelegateVsComparerBenchmark.cs b/Recyclable.Collections.Benchmarks/DelegateVsComparerBenchmark.cs deleted file mode 100644 index 2eb4204..0000000 --- a/Recyclable.Collections.Benchmarks/DelegateVsComparerBenchmark.cs +++ /dev/null @@ -1,252 +0,0 @@ -using BenchmarkDotNet.Attributes; -using MiscUtil; - -namespace Recyclable.Collections.Benchmarks -{ - public delegate T Compare(T x, T y); - - public readonly struct MyComparerReadOnlyStruct : IComparer - { - public int Compare(T? x, T? y) => Operator.Subtract(x, y) switch - { - > 0 => 1, - < 0 => -1, - _ => 0, - }; - } - - public struct MyComparerStruct : IComparer - { - public int Compare(T? x, T? y) => Operator.Subtract(x, y) switch - { - > 0 => 1, - < 0 => -1, - _ => 0, - }; - } - - public class MyComparer : IComparer - { - public int Compare(long x, long y) => (x - y) switch - { - > 0 => 1, - < 0 => -1, - _ => 0, - }; - } - - [MemoryDiagnoser] - public class DelegateVsComparerBenchmark - { - private static readonly Comparer _comparer = Comparer.Default; - - private const int _objectCount = 1_000_000; - private static readonly IEnumerable _testObjects = Enumerable.Range(1, _objectCount).Select(x => (long)x); - private static readonly IEnumerable> _testListOfFunctions = _testObjects.Select>(static x => new MyComparer().Compare); - private static readonly IEnumerable> _testListOfComparers = _testObjects.Select(x => (IComparer)new MyComparer()); - private static readonly IEnumerable> _testListOfDelegates = _testObjects.Select(x => new Compare((x, y) => new MyComparer().Compare(x, y))); - private static readonly IEnumerable> _testListOfLocalFunctions = GetLocalFunctions(_testObjects); - private static readonly IEnumerable _testListOfClasses = _testObjects.Select(x => new MyComparer()); - private static readonly IEnumerable> _testListOfStructs = _testObjects.Select(x => new MyComparerStruct()); - private static readonly IEnumerable> _testListOfBoxedStructs = _testObjects.Select(x => (IComparer)new MyComparerStruct()); - private static IEnumerable _testListOfUnboxedStructs() where TComparer : IComparer, new() - { - foreach (var item in _testObjects) - { - yield return new TComparer(); - } - } - - private static IEnumerable> GetLocalFunctions(IEnumerable testObjects) - { - static int compareFunc(long x, long y) - { - return (x - y) switch - { - > 0 => 1, - < 0 => -1, - _ => 0 - }; - } - - foreach (var item in testObjects) - { - yield return compareFunc; - } - } - - private static void TestWithComparer(IComparer comparer) - { - var testData = _testObjects.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length - 1; itemIdx++) - { - _ = comparer.Compare(testData[itemIdx], testData[itemIdx + 1]); - } - } - - //[Benchmark(Baseline = true)] - public void Comparer() - { - TestWithComparer(_comparer); - } - - private static void TestWithFunc(Func compareFunc) - { - var testData = _testObjects.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length - 1; itemIdx++) - { - _ = compareFunc(testData[itemIdx], testData[itemIdx + 1]); - } - } - - //[Benchmark] - public void Func() - { - TestWithFunc((x, y) => (x - y) switch - { - > 0 => 1, - < 0 => -1, - _ => 0 - }); - } - - //[Benchmark] - public void StaticFunc() - { - TestWithFunc(static (x, y) => (x - y) switch - { - > 0 => 1, - < 0 => -1, - _ => 0 - }); - } - - private static void TestWithDelegate(Compare compareFunc) - { - var testData = _testObjects.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length - 1; itemIdx++) - { - _ = compareFunc(testData[itemIdx], testData[itemIdx + 1]); - } - } - - //[Benchmark] - public void Delegate() - { - TestWithDelegate((x, y) => (x - y) switch - { - > 0 => 1, - < 0 => -1, - _ => 0 - }); - } - - //[Benchmark] - public void ListOfFuncs() - { - const long x = 2, y = 5; - var testData = _testListOfFunctions.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) - { - var compareFunc = testData[itemIdx]; - _ = compareFunc(x, y); - } - } - - //[Benchmark] - public void ListOfLocalFuncs() - { - const long x = 2, y = 5; - var testData = _testListOfLocalFunctions.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) - { - var compareFunc = testData[itemIdx]; - _ = compareFunc(x, y); - } - } - - //[Benchmark] - public void ListOfComparers() - { - const long x = 2, y = 5; - var testData = _testListOfComparers.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) - { - var comparer = testData[itemIdx]; - _ = comparer.Compare(x, y); - } - } - - //[Benchmark] - public void ListOfClasses() - { - const long x = 2, y = 5; - var testData = _testListOfClasses.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) - { - var comparer = testData[itemIdx]; - _ = comparer.Compare(x, y); - } - } - - //[Benchmark] - public void ListOfStructs() - { - const long x = 2, y = 5; - var testData = _testListOfStructs.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) - { - var comparer = testData[itemIdx]; - _ = comparer.Compare(x, y); - } - } - - //[Benchmark] - public void ListOfBoxedStructs() - { - const long x = 2, y = 5; - var testData = _testListOfBoxedStructs.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) - { - var comparer = testData[itemIdx]; - _ = comparer.Compare(x, y); - } - } - - //[Benchmark] - public void ListOfUnboxedStructs() - { - const long x = 2, y = 5; - var testData = _testListOfUnboxedStructs, long>().ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) - { - var comparer = testData[itemIdx]; - _ = comparer.Compare(x, y); - } - } - - //[Benchmark] - public void ListOfUnboxedReadOnlyStructs() - { - const long x = 2, y = 5; - var testData = _testListOfUnboxedStructs, long>().ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) - { - var comparer = testData[itemIdx]; - _ = comparer.Compare(x, y); - } - } - - //[Benchmark] - public void ListOfDelegates() - { - const long x = 2, y = 5; - var testData = _testListOfDelegates.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) - { - var compareDelegate = testData[itemIdx]; - _ = compareDelegate(x, y); - } - } - } -} diff --git a/Recyclable.Collections.Benchmarks/ModuloBenchmarks.cs b/Recyclable.Collections.Benchmarks/ModuloBenchmarks.cs deleted file mode 100644 index dfa9d3f..0000000 --- a/Recyclable.Collections.Benchmarks/ModuloBenchmarks.cs +++ /dev/null @@ -1,355 +0,0 @@ -using BenchmarkDotNet.Attributes; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; - -namespace Recyclable.Collections.Benchmarks -{ - [MemoryDiagnoser] - public class ModuloBenchmarks - { - private long _cutoff; - private Vector128 _increment; - private long _lastCaseIndex; - - [GlobalSetup] - public void Setup() - { - _cutoff = BlockSize << 4; - _increment = Vector128.Create(-BlockSize, 1L); - _lastCaseIndex = 0; - } - - private static void DoNothing(T result, T remainder, [CallerMemberName] string? callerName = null) - { - //Console.WriteLine($"{callerName}: result = {result}, remainder = {remainder}"); - } - - //[Params(123, 10, 2, int.MaxValue, int.MaxValue / 2, int.MaxValue / 3, int.MaxValue / 4, int.MaxValue / 6, int.MaxValue / 10, int.MaxValue / 15, int.MaxValue / 20)] - [Params(int.MaxValue, int.MaxValue / 2, int.MaxValue / 3, int.MaxValue / 4, int.MaxValue / 6, int.MaxValue / 8, int.MaxValue / 10, int.MaxValue / 100)] - //[Params(int.MaxValue / 10, int.MaxValue / 20, int.MaxValue / 100)] - public int BlockSize { get; set; } = int.MaxValue; - - [Params(int.MaxValue, int.MaxValue / 1000, int.MaxValue / 10000)] - //[Params(int.MaxValue)] - //[Params(int.MaxValue * 100L)] - public long TestNumber { get; set; } = int.MaxValue; - - private void CalculateDividerAndModulusWithOperator(long i) - { - DoNothing(i / BlockSize, i % BlockSize); - } - - private void CalculateWithMathDivRem(long i) - { - DoNothing(Math.DivRem(i, BlockSize, out long remainder), remainder); - } - - private void CalculateWithAddWithFor(long i) - { - int result = 0; - int remainder = 0; - int blockSize = BlockSize; - for (long iteration = 0; iteration < i; iteration++) - { - if (remainder > blockSize - 1) - { - remainder = 0; - result++; - } - - remainder++; - } - - DoNothing(result, remainder); - } - - private void CalculateWithAddWithForOptimized(long i) - { - long total = 0; - int result = 0; - int blockSize = BlockSize; - while (total + blockSize <= i) - { - total += blockSize; - result++; - } - - DoNothing(result, i - total); - } - - private void CalculateWithXDivYMulX(long i) - { - DoNothing(i / BlockSize, i - (i / BlockSize * BlockSize)); - } - - private void CalculateWithXDivYMulXWithLocalVar(long i) - { - int blockSize = BlockSize; - int result = (int)(i / blockSize); - DoNothing(result, i - (result * blockSize)); - } - - private void CalculateWithSubtractWithWhile(long i) - { - int result = 0; - long remainder = i; - int blockSize = BlockSize; - while (remainder >= blockSize) - { - remainder -= blockSize; - result++; - } - - DoNothing(result, remainder); - } - - private void CalculateWithSubractWithWhileWithSse2(long i) - { - var state = Vector128.Create(i, 0); - int blockSize = BlockSize; - var inc = _increment; - while (state.GetElement(0) >= blockSize) - { - state = Sse2.Add(state, inc); - } - - DoNothing(state.GetElement(1), state.GetElement(0)); - } - - private void CalculateWithMixedApproachWithSubractWithWhile(long i) - { - long remainder; - if (i >= _cutoff) - { - DoNothing(Math.DivRem(i, BlockSize, out remainder), remainder); - } - else - { - int result = 0; - int blockSize = BlockSize; - remainder = i; - while (remainder >= blockSize) - { - remainder -= blockSize; - result++; - } - - DoNothing(result, remainder); - } - } - - private void CalculateWithMixedApproachWithSubtractWithWhileAsVector(long i) - { - if (i >= _cutoff) - { - DoNothing(Math.DivRem(i, BlockSize, out long remainder), remainder); - } - else - { - var state = new Vector(new[] { 0L, i, 0, 0 }); - var increment = new Vector(new[] { 1L, -BlockSize, 0, 0 }); - int blockSize = BlockSize; - while (state[1] >= blockSize) - { - state += increment; - } - - DoNothing(state[0], state[1]); - } - } - - private void CalculateWithMixedApproachWithSubtractWithWhileWithSse2(long i) - { - if (i >= _cutoff) - { - DoNothing(Math.DivRem(i, BlockSize, out long remainder), remainder); - } - else - { - var increment = _increment; - var state = Vector128.Create(i, 0); - int blockSize = BlockSize; - while (state.GetElement(0) >= blockSize) - { - state = Sse2.Add(state, increment); - } - - DoNothing(state.GetElement(1), state.GetElement(0)); - } - } - - private void CalculateWithMixedApproachWithSubtractWithStackalloc(long i) - { - if (i >= _cutoff) - { - DoNothing(Math.DivRem(i, BlockSize, out long remainder), remainder); - } - else - { - Span state = stackalloc long[2] { i, 0 };// Vector128.Create(BlockSize, 0); - int blockSize = BlockSize; - while (state[0] >= blockSize) - { - state[0] -= blockSize; - state[1]++; - } - - DoNothing(state[1], state[0]); - } - } - - private void CalculateWithMixedApproachWithForLoopOptimized(long i) - { - long total; - if (i >= _cutoff) - { - DoNothing(Math.DivRem(i, BlockSize, out total), total); - } - else - { - total = 0; - int result = 0; - int blockSize = BlockSize; - while (total + blockSize <= i) - { - total += blockSize; - result++; - } - - DoNothing(result, i - total); - } - } - - [Benchmark(Baseline = true)] - public void DividerAndModulusWithOperator() - { - long testNumber = TestNumber; - for (long i = testNumber; i >= _lastCaseIndex; i--) - { - CalculateDividerAndModulusWithOperator(i); - } - } - - [Benchmark] - public void MathDivRem() - { - long testNumber = TestNumber; - for (long i = testNumber; i >= _lastCaseIndex; i--) - { - CalculateWithMathDivRem(i); - } - } - - [Benchmark] - public void AddWithFor() - { - long testNumber = TestNumber; - for (long i = testNumber; i >= _lastCaseIndex; i--) - { - CalculateWithAddWithFor(i); - } - } - - [Benchmark(Baseline = true)] - public void AddWithForOptimized() - { - long testNumber = TestNumber; - for (long i = testNumber; i >= _lastCaseIndex; i--) - { - CalculateWithAddWithForOptimized(i); - } - } - - [Benchmark] - public void XDivYMulX() - { - long testNumber = TestNumber; - for (long i = testNumber; i >= _lastCaseIndex; i--) - { - CalculateWithXDivYMulX(i); - } - } - - [Benchmark] - public void XDivYMulXWithLocalVar() - { - long testNumber = TestNumber; - for (long i = testNumber; i >= _lastCaseIndex; i--) - { - CalculateWithXDivYMulXWithLocalVar(i); - } - } - - [Benchmark] - public void SubtractWithWhile() - { - long testNumber = TestNumber; - for (long i = testNumber; i >= _lastCaseIndex; i--) - { - CalculateWithSubtractWithWhile(i); - } - } - - [Benchmark] - public void SubtractWithWhileWithSse2() - { - long testNumber = TestNumber; - for (long i = testNumber; i >= _lastCaseIndex; i--) - { - CalculateWithSubractWithWhileWithSse2(i); - } - } - - [Benchmark(Baseline = false)] - public void MixedApproachWithSubtractWithWhile() - { - long testNumber = TestNumber; - for (long i = testNumber; i >= _lastCaseIndex; i--) - { - CalculateWithMixedApproachWithSubractWithWhile(i); - } - } - - [Benchmark(Baseline = false)] - public void MixedApproachWithSubtractWithWhileAsVector() - { - long testNumber = TestNumber; - for (long i = testNumber; i >= _lastCaseIndex; i--) - { - CalculateWithMixedApproachWithSubtractWithWhileAsVector(i); - } - } - - [Benchmark(Baseline = false)] - public void MixedApproachWithSubtractWithWhileWithSse2() - { - long testNumber = TestNumber; - for (long i = testNumber; i >= _lastCaseIndex; i--) - { - CalculateWithMixedApproachWithSubtractWithWhileWithSse2(i); - } - } - - [Benchmark(Baseline = false)] - public void MixedApproachWithSubtractWithStackalloc() - { - long testNumber = TestNumber; - for (long i = testNumber; i >= _lastCaseIndex; i--) - { - CalculateWithMixedApproachWithSubtractWithStackalloc(i); - } - } - - [Benchmark(Baseline = false)] - public void MixedApproachWithForLoopOptimized() - { - long testNumber = TestNumber; - for (long i = testNumber; i >= _lastCaseIndex; i--) - { - CalculateWithMixedApproachWithForLoopOptimized(i); - } - } - } -} \ No newline at end of file diff --git a/Recyclable.Collections.Benchmarks/POC/ArraySizeLimitPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/ArraySizeLimitPoc.Benchmarks.cs new file mode 100644 index 0000000..42c5783 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/ArraySizeLimitPoc.Benchmarks.cs @@ -0,0 +1,55 @@ +using BenchmarkDotNet.Attributes; +using System.Buffers; + +namespace Recyclable.Collections.Benchmarks.POC +{ + public class ArraySizeLimitPocBenchmarks : PocBenchmarkBase + { + public static void Run() + { + var benchmark = new ArraySizeLimitPocBenchmarks(); + benchmark.FindArraySizeLimit(); + } + + [Benchmark] + public void FindArraySizeLimit() + { + var pool = ArrayPool.Shared; + var bufferSize = int.MaxValue; + var oldValue = 0; + while (true) + { + try + { + Console.WriteLine($"Allocating {bufferSize}"); + var arr = pool.Rent((int)bufferSize); + var nextValue = (oldValue - bufferSize) >> 1; + if (nextValue == 0) + { + break; + } + + oldValue = bufferSize; + bufferSize += nextValue; + + pool.Return(arr); + GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); + } + catch (Exception) + { + Console.WriteLine($"Error during array creation when {nameof(bufferSize)} = {bufferSize}"); + var nextValue = (oldValue - bufferSize) >> 1; + if (nextValue == 0) + { + break; + } + + oldValue = bufferSize; + bufferSize -= nextValue; + } + } + + Console.WriteLine("All done"); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/POC/DelegateVsComparerPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/DelegateVsComparerPoc.Benchmarks.cs new file mode 100644 index 0000000..4cd9e75 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/DelegateVsComparerPoc.Benchmarks.cs @@ -0,0 +1,274 @@ +using BenchmarkDotNet.Attributes; +using MiscUtil; + +namespace Recyclable.Collections.Benchmarks.POC +{ + [MemoryDiagnoser] + public class DelegateVsComparerPocBenchmarks : PocBenchmarkBase + { + #region Dummy classes + + private delegate T Compare(T x, T y); + + public readonly struct MyComparerReadOnlyStruct : IComparer + { + public int Compare(T? x, T? y) => Operator.Subtract(x, y) switch + { + > 0 => 1, + < 0 => -1, + _ => 0, + }; + } + + private struct MyComparerStruct : IComparer + { + public int Compare(T? x, T? y) => Operator.Subtract(x, y) switch + { + > 0 => 1, + < 0 => -1, + _ => 0, + }; + } + + private class MyComparer : IComparer + { + public int Compare(long x, long y) => (x - y) switch + { + > 0 => 1, + < 0 => -1, + _ => 0, + }; + } + + #endregion + + public static void Run() + { + var benchmark = new DelegateVsComparerPocBenchmarks(); + benchmark.Comparer(); + benchmark.Delegate(); + benchmark.Func(); + benchmark.ListOfBoxedStructs(); + benchmark.ListOfClasses(); + benchmark.ListOfComparers(); + benchmark.ListOfDelegates(); + benchmark.ListOfFuncs(); + benchmark.ListOfLocalFuncs(); + benchmark.ListOfStructs(); + benchmark.ListOfUnboxedReadOnlyStructs(); + benchmark.ListOfUnboxedStructs(); + benchmark.StaticFunc(); + } + + private static readonly Comparer _comparer = Comparer.Default; + + private const int _objectCount = 1_000_000; + private static readonly IEnumerable _testObjects = Enumerable.Range(1, _objectCount).Select(x => (long)x); + private static readonly IEnumerable> _testListOfFunctions = _testObjects.Select>(static x => new MyComparer().Compare); + private static readonly IEnumerable> _testListOfComparers = _testObjects.Select(x => (IComparer)new MyComparer()); + private static readonly IEnumerable> _testListOfDelegates = _testObjects.Select(x => new Compare((x, y) => new MyComparer().Compare(x, y))); + private static readonly IEnumerable> _testListOfLocalFunctions = GetLocalFunctions(_testObjects); + private static readonly IEnumerable _testListOfClasses = _testObjects.Select(x => new MyComparer()); + private static readonly IEnumerable> _testListOfStructs = _testObjects.Select(x => new MyComparerStruct()); + private static readonly IEnumerable> _testListOfBoxedStructs = _testObjects.Select(x => (IComparer)new MyComparerStruct()); + private static IEnumerable _testListOfUnboxedStructs() where TComparer : IComparer, new() + { + foreach (var item in _testObjects) + { + yield return new TComparer(); + } + } + + private static IEnumerable> GetLocalFunctions(IEnumerable testObjects) + { + static int compareFunc(long x, long y) + { + return (x - y) switch + { + > 0 => 1, + < 0 => -1, + _ => 0 + }; + } + + foreach (var item in testObjects) + { + yield return compareFunc; + } + } + + private static void TestWithComparer(IComparer comparer) + { + var testData = _testObjects.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length - 1; itemIdx++) + { + _ = comparer.Compare(testData[itemIdx], testData[itemIdx + 1]); + } + } + + [Benchmark(Baseline = true)] + public void Comparer() + { + TestWithComparer(_comparer); + } + + private static void TestWithFunc(Func compareFunc) + { + var testData = _testObjects.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length - 1; itemIdx++) + { + _ = compareFunc(testData[itemIdx], testData[itemIdx + 1]); + } + } + + [Benchmark] + public void Func() + { + TestWithFunc((x, y) => (x - y) switch + { + > 0 => 1, + < 0 => -1, + _ => 0 + }); + } + + [Benchmark] + public void StaticFunc() + { + TestWithFunc(static (x, y) => (x - y) switch + { + > 0 => 1, + < 0 => -1, + _ => 0 + }); + } + + private static void TestWithDelegate(Compare compareFunc) + { + var testData = _testObjects.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length - 1; itemIdx++) + { + _ = compareFunc(testData[itemIdx], testData[itemIdx + 1]); + } + } + + [Benchmark] + public void Delegate() + { + TestWithDelegate((x, y) => (x - y) switch + { + > 0 => 1, + < 0 => -1, + _ => 0 + }); + } + + [Benchmark] + public void ListOfFuncs() + { + const long x = 2, y = 5; + var testData = _testListOfFunctions.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) + { + var compareFunc = testData[itemIdx]; + _ = compareFunc(x, y); + } + } + + [Benchmark] + public void ListOfLocalFuncs() + { + const long x = 2, y = 5; + var testData = _testListOfLocalFunctions.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) + { + var compareFunc = testData[itemIdx]; + _ = compareFunc(x, y); + } + } + + [Benchmark] + public void ListOfComparers() + { + const long x = 2, y = 5; + var testData = _testListOfComparers.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) + { + var comparer = testData[itemIdx]; + _ = comparer.Compare(x, y); + } + } + + [Benchmark] + public void ListOfClasses() + { + const long x = 2, y = 5; + var testData = _testListOfClasses.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) + { + var comparer = testData[itemIdx]; + _ = comparer.Compare(x, y); + } + } + + [Benchmark] + public void ListOfStructs() + { + const long x = 2, y = 5; + var testData = _testListOfStructs.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) + { + var comparer = testData[itemIdx]; + _ = comparer.Compare(x, y); + } + } + + [Benchmark] + public void ListOfBoxedStructs() + { + const long x = 2, y = 5; + var testData = _testListOfBoxedStructs.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) + { + var comparer = testData[itemIdx]; + _ = comparer.Compare(x, y); + } + } + + [Benchmark] + public void ListOfUnboxedStructs() + { + const long x = 2, y = 5; + var testData = _testListOfUnboxedStructs, long>().ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) + { + var comparer = testData[itemIdx]; + _ = comparer.Compare(x, y); + } + } + + [Benchmark] + public void ListOfUnboxedReadOnlyStructs() + { + const long x = 2, y = 5; + var testData = _testListOfUnboxedStructs, long>().ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) + { + var comparer = testData[itemIdx]; + _ = comparer.Compare(x, y); + } + } + + [Benchmark] + public void ListOfDelegates() + { + const long x = 2, y = 5; + var testData = _testListOfDelegates.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) + { + var compareDelegate = testData[itemIdx]; + _ = compareDelegate(x, y); + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/POC/ModuloPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/ModuloPoc.Benchmarks.cs new file mode 100644 index 0000000..d5f4ddb --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/ModuloPoc.Benchmarks.cs @@ -0,0 +1,374 @@ +using BenchmarkDotNet.Attributes; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace Recyclable.Collections.Benchmarks.POC +{ + [MemoryDiagnoser] + public class ModuloPocBenchmarks + { + public static void Run() + { + var benchmark = new ModuloPocBenchmarks(); + benchmark.Setup(); + benchmark.AddWithFor(); + benchmark.AddWithForOptimized(); + benchmark.DividerAndModulusWithOperator(); + benchmark.MathDivRem(); + benchmark.MixedApproachWithForLoopOptimized(); + benchmark.MixedApproachWithSubtractWithStackalloc(); + benchmark.MixedApproachWithSubtractWithWhile(); + benchmark.MixedApproachWithSubtractWithWhileAsVector(); + benchmark.MixedApproachWithSubtractWithWhileWithSse2(); + benchmark.SubtractWithWhile(); + benchmark.SubtractWithWhileWithSse2(); + benchmark.XDivYMulX(); + benchmark.XDivYMulXWithLocalVar(); + } + + private long _cutoff; + private Vector128 _increment; + private long _lastCaseIndex; + + [GlobalSetup] + public void Setup() + { + _cutoff = BlockSize << 4; + _increment = Vector128.Create(-BlockSize, 1L); + _lastCaseIndex = 0; + } + + private static void DoNothing(T result, T remainder, [CallerMemberName] string? callerName = null) + { + //Console.WriteLine($"{callerName}: result = {result}, remainder = {remainder}"); + } + + //[Params(123, 10, 2, int.MaxValue, int.MaxValue / 2, int.MaxValue / 3, int.MaxValue / 4, int.MaxValue / 6, int.MaxValue / 10, int.MaxValue / 15, int.MaxValue / 20)] + [Params(int.MaxValue, int.MaxValue / 2, int.MaxValue / 3, int.MaxValue / 4, int.MaxValue / 6, int.MaxValue / 8, int.MaxValue / 10, int.MaxValue / 100)] + //[Params(int.MaxValue / 10, int.MaxValue / 20, int.MaxValue / 100)] + public int BlockSize { get; set; } = int.MaxValue; + + [Params(int.MaxValue, int.MaxValue / 1000, int.MaxValue / 10000)] + //[Params(int.MaxValue)] + //[Params(int.MaxValue * 100L)] + public long TestNumber { get; set; } = int.MaxValue; + + private void CalculateDividerAndModulusWithOperator(long i) + { + DoNothing(i / BlockSize, i % BlockSize); + } + + private void CalculateWithMathDivRem(long i) + { + DoNothing(Math.DivRem(i, BlockSize, out long remainder), remainder); + } + + private void CalculateWithAddWithFor(long i) + { + int result = 0; + int remainder = 0; + int blockSize = BlockSize; + for (long iteration = 0; iteration < i; iteration++) + { + if (remainder > blockSize - 1) + { + remainder = 0; + result++; + } + + remainder++; + } + + DoNothing(result, remainder); + } + + private void CalculateWithAddWithForOptimized(long i) + { + long total = 0; + int result = 0; + int blockSize = BlockSize; + while (total + blockSize <= i) + { + total += blockSize; + result++; + } + + DoNothing(result, i - total); + } + + private void CalculateWithXDivYMulX(long i) + { + DoNothing(i / BlockSize, i - (i / BlockSize * BlockSize)); + } + + private void CalculateWithXDivYMulXWithLocalVar(long i) + { + int blockSize = BlockSize; + int result = (int)(i / blockSize); + DoNothing(result, i - (result * blockSize)); + } + + private void CalculateWithSubtractWithWhile(long i) + { + int result = 0; + long remainder = i; + int blockSize = BlockSize; + while (remainder >= blockSize) + { + remainder -= blockSize; + result++; + } + + DoNothing(result, remainder); + } + + private void CalculateWithSubractWithWhileWithSse2(long i) + { + var state = Vector128.Create(i, 0); + int blockSize = BlockSize; + var inc = _increment; + while (state.GetElement(0) >= blockSize) + { + state = Sse2.Add(state, inc); + } + + DoNothing(state.GetElement(1), state.GetElement(0)); + } + + private void CalculateWithMixedApproachWithSubractWithWhile(long i) + { + long remainder; + if (i >= _cutoff) + { + DoNothing(Math.DivRem(i, BlockSize, out remainder), remainder); + } + else + { + int result = 0; + int blockSize = BlockSize; + remainder = i; + while (remainder >= blockSize) + { + remainder -= blockSize; + result++; + } + + DoNothing(result, remainder); + } + } + + private void CalculateWithMixedApproachWithSubtractWithWhileAsVector(long i) + { + if (i >= _cutoff) + { + DoNothing(Math.DivRem(i, BlockSize, out long remainder), remainder); + } + else + { + var state = new Vector(new[] { 0L, i, 0, 0 }); + var increment = new Vector(new[] { 1L, -BlockSize, 0, 0 }); + int blockSize = BlockSize; + while (state[1] >= blockSize) + { + state += increment; + } + + DoNothing(state[0], state[1]); + } + } + + private void CalculateWithMixedApproachWithSubtractWithWhileWithSse2(long i) + { + if (i >= _cutoff) + { + DoNothing(Math.DivRem(i, BlockSize, out long remainder), remainder); + } + else + { + var increment = _increment; + var state = Vector128.Create(i, 0); + int blockSize = BlockSize; + while (state.GetElement(0) >= blockSize) + { + state = Sse2.Add(state, increment); + } + + DoNothing(state.GetElement(1), state.GetElement(0)); + } + } + + private void CalculateWithMixedApproachWithSubtractWithStackalloc(long i) + { + if (i >= _cutoff) + { + DoNothing(Math.DivRem(i, BlockSize, out long remainder), remainder); + } + else + { + Span state = stackalloc long[2] { i, 0 };// Vector128.Create(BlockSize, 0); + int blockSize = BlockSize; + while (state[0] >= blockSize) + { + state[0] -= blockSize; + state[1]++; + } + + DoNothing(state[1], state[0]); + } + } + + private void CalculateWithMixedApproachWithForLoopOptimized(long i) + { + long total; + if (i >= _cutoff) + { + DoNothing(Math.DivRem(i, BlockSize, out total), total); + } + else + { + total = 0; + int result = 0; + int blockSize = BlockSize; + while (total + blockSize <= i) + { + total += blockSize; + result++; + } + + DoNothing(result, i - total); + } + } + + [Benchmark(Baseline = true)] + public void DividerAndModulusWithOperator() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateDividerAndModulusWithOperator(i); + } + } + + [Benchmark] + public void MathDivRem() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithMathDivRem(i); + } + } + + [Benchmark] + public void AddWithFor() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithAddWithFor(i); + } + } + + [Benchmark(Baseline = true)] + public void AddWithForOptimized() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithAddWithForOptimized(i); + } + } + + [Benchmark] + public void XDivYMulX() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithXDivYMulX(i); + } + } + + [Benchmark] + public void XDivYMulXWithLocalVar() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithXDivYMulXWithLocalVar(i); + } + } + + [Benchmark] + public void SubtractWithWhile() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithSubtractWithWhile(i); + } + } + + [Benchmark] + public void SubtractWithWhileWithSse2() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithSubractWithWhileWithSse2(i); + } + } + + [Benchmark(Baseline = false)] + public void MixedApproachWithSubtractWithWhile() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithMixedApproachWithSubractWithWhile(i); + } + } + + [Benchmark(Baseline = false)] + public void MixedApproachWithSubtractWithWhileAsVector() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithMixedApproachWithSubtractWithWhileAsVector(i); + } + } + + [Benchmark(Baseline = false)] + public void MixedApproachWithSubtractWithWhileWithSse2() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithMixedApproachWithSubtractWithWhileWithSse2(i); + } + } + + [Benchmark(Baseline = false)] + public void MixedApproachWithSubtractWithStackalloc() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithMixedApproachWithSubtractWithStackalloc(i); + } + } + + [Benchmark(Baseline = false)] + public void MixedApproachWithForLoopOptimized() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithMixedApproachWithForLoopOptimized(i); + } + } + } +} \ No newline at end of file diff --git a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs new file mode 100644 index 0000000..552979a --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs @@ -0,0 +1,25 @@ +namespace Recyclable.Collections.Benchmarks.POC +{ + public class PocBenchmarkBase + { + // 0 2 4 8 16 32 64 128 256 512 1024 2048 4096 8192 16384 32768 65536 131072 262144 524288 + // 1048576 2097152 4194304 8388608 16777216 33554432 67108864 134217728 268435456 536870912 + // 1073741824 2147483648 + + //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] + //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] + public long TestObjectCount = 1_000_000; + + //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200)] + //[Params(10, 20, 50, 100)] + //[Params(1, 10)] + public int Divider = 10; + //public int BlockSize = 1_048_576;//int.MaxValue - 1; + public int BlockSize => TestObjectCount switch + { + 0 => 1, + > 0 and <= 10 => (int)TestObjectCount, + _ => TestObjectCount / Divider > 0 ? checked((int)(TestObjectCount / Divider)) : checked((int)TestObjectCount) + }; + } +} diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs new file mode 100644 index 0000000..c0efe8b --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.EnsureCapacity.cs @@ -0,0 +1,908 @@ +using BenchmarkDotNet.Attributes; +using System.Buffers; +using System.Collections; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Recyclable.Collections.Benchmarks.POC +{ + public partial class RecyclableListPocBenchmarks + { + #region Old class + + private class OldRecyclableList : IDisposable, IList + { + private const int ItemNotFoundIndex = -1; + + private static readonly ArrayPool _defaultMemoryBlocksPool = ArrayPool.Create(); + private static readonly ArrayPool _defaultBlockArrayPool = ArrayPool.Create(); + private static readonly T[][] _emptyMemoryBlocksArray = new T[0][]; + private static readonly T[] _emptyBlockArray = new T[0]; + + private int _blockSize; + private int _blockSizePow2Shift; + private int _nextItemBlockIndex; + private int _nextItemIndex; + + protected static readonly bool NeedsClearing = !typeof(T).IsValueType; + + protected ArrayPool _memoryBlocksPool; + protected ArrayPool _blockArrayPool; + protected T[][] _memoryBlocks; + + private long _capacity; + public long Capacity + { + get => _capacity; + protected set => _capacity = value; + } + + public int Count => checked((int)_longCount); + public bool IsReadOnly { get; } + public int LastTakenBlockIndex => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1); + + protected long _longCount; + + public long LongCount + { + get => _longCount; + set => _longCount = value; + } + + public int ReservedBlockCount => (int)(_capacity >> _blockSizePow2Shift) + ((_capacity & (_blockSize - 1)) > 0 ? 1 : 0); + public int BlockSize => _blockSize; + public int NextItemBlockIndex => _nextItemBlockIndex; + public int NextItemIndex => _nextItemIndex; + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private void DoRemoveAt(long index) + { + int blockSize = _blockSize; + int nextItemIndex = _nextItemIndex; + T[][] memoryBlocks = _memoryBlocks; + _longCount--; + if (index < _longCount) + { + int sourceBlockIndex = (int)((index + 1) >> _blockSizePow2Shift); + int sourceItemIndex = (int)((index + 1) & (blockSize - 1)); + + int targetBlockIndex = (int)(index >> _blockSizePow2Shift); + int targetItemIndex = (int)(index & (blockSize - 1)); + + int lastTakenBlockIndex = LastTakenBlockIndex; + while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != _nextItemBlockIndex))) + { + int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0 + ? Math.Min(blockSize - sourceItemIndex, blockSize - targetItemIndex) + : Math.Min(nextItemIndex, blockSize - targetItemIndex); + + Array.Copy(memoryBlocks[sourceBlockIndex], sourceItemIndex, memoryBlocks[targetBlockIndex], targetItemIndex, toCopy); + // We didn't have enough room in the target array block. There are still items in the source array block to copy. + if (sourceItemIndex + toCopy < blockSize) + { + sourceItemIndex += toCopy; + targetBlockIndex++; + targetItemIndex = 0; + } + // We copied all the source items in the current array block. But have we filled the target? + else + { + sourceItemIndex = 0; + sourceBlockIndex++; + if (targetItemIndex + toCopy < blockSize) + { + targetItemIndex += toCopy; + } + else + { + targetBlockIndex++; + targetItemIndex = 0; + } + } + } + } + + if (nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = blockSize - 1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + new Span(memoryBlocks[_nextItemBlockIndex], _nextItemIndex, 1)[0] = default; +#pragma warning restore CS8601 + } + } + + private static void ThrowArgumentOutOfRangeException(in string message) => throw new ArgumentOutOfRangeException("index", message); + + protected void AddRangeEnumerated(IEnumerable source, int growByCount) + { + long i, copied = _longCount; + + int blockSize = _blockSize, targetItemIdx = _nextItemIndex, targetBlockIdx = _nextItemBlockIndex; + + using IEnumerator enumerator = source.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return; + } + + long capacity = _capacity; + long available = capacity - copied; + + Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); + Span blockArraySpan; + var memoryBlocksCount = memoryBlocksSpan.Length; + while (true) + { + if (copied + growByCount > capacity) + { + capacity = EnsureCapacity(capacity + growByCount); + memoryBlocksCount = _memoryBlocks.Length; + if (blockSize != _blockSize) + { + blockSize = _blockSize; + } + + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + available = capacity - copied; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + for (i = 1; i <= available; i++) + { + blockArraySpan[targetItemIdx++] = enumerator.Current; + + if (!enumerator.MoveNext()) + { + if (targetItemIdx < blockSize) + { + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + } + else + { + _nextItemBlockIndex = targetBlockIdx + 1; + _nextItemIndex = 0; + } + + _longCount += copied + i - _longCount; + return; + } + + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + copied += available; + } + } + + protected void AddRangeWithKnownCount(IEnumerable source, int requiredAdditionalCapacity) + { + long copied = _longCount; + int blockSize = _blockSize; + int targetItemIdx = _nextItemIndex; + int targetBlockIdx = _nextItemBlockIndex; + + Span memoryBlocksSpan; + Span blockArraySpan; + long requiredCapacity = copied + requiredAdditionalCapacity; + if (_capacity < requiredCapacity) + { + _ = EnsureCapacity(requiredCapacity); + blockSize = _blockSize; + } + + var memoryBlocksCount = ReservedBlockCount; + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + foreach (var item in source) + { + blockArraySpan[targetItemIdx++] = item; + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + _longCount = requiredCapacity; + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + return; + } + + protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) + { + Span memoryBlocksSpan = new(memoryBlocks, 0, lastBlockIndex + 1); + int itemIndex, blockIndex; + for (blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); + if (itemIndex >= 0) + { + return itemIndex + (blockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))); + } + } + + if (blockIndex == lastBlockIndex) + { + itemIndex = nextItemIndex != 0 + ? Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, nextItemIndex) + : Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, blockSize); + } + else + { + return ItemNotFoundIndex; + } + + return itemIndex >= 0 ? itemIndex + (lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndex; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected static long Resize(ref T[][]? source, int minBlockSize, long oldCapacity, long newCapacity, ArrayPool memoryBlocksPool, ArrayPool blockArrayPool) + { + int minBlockSizePowerToShift = 31 - BitOperations.LeadingZeroCount((uint)minBlockSize); + int sourceBlockCount = (int)(oldCapacity >> minBlockSizePowerToShift); + int requiredBlockCount = (int)(newCapacity >> minBlockSizePowerToShift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0); + int blockIndex; + T[][]? newMemoryBlocks; + Span memoryBlocksSpan; + + // Release excessive blocks if we're downsizing + if (requiredBlockCount < sourceBlockCount && (minBlockSize >= RecyclableDefaults.MinPooledArrayLength)) + { + memoryBlocksSpan = new(source, requiredBlockCount, sourceBlockCount - requiredBlockCount); + blockIndex = 0; + while (blockIndex < memoryBlocksSpan.Length) + { + blockArrayPool.Return(memoryBlocksSpan[blockIndex++]!, NeedsClearing); + } + } + + // Allocate new memory block for all arrays + newMemoryBlocks = requiredBlockCount < RecyclableDefaults.MinPooledArrayLength + ? (new T[requiredBlockCount][]) + : memoryBlocksPool.Rent(requiredBlockCount); + + // Copy arrays from the old memory block for all arrays + if (sourceBlockCount > 0) + { + Array.Copy(source!, newMemoryBlocks, sourceBlockCount); + // We can now return the old memory block for all arrays itself + if (sourceBlockCount >= RecyclableDefaults.MinPooledArrayLength) + { + memoryBlocksPool.Return(source!, true); + } + } + + source = newMemoryBlocks; + + // Allocate arrays for any new blocks + if (requiredBlockCount > sourceBlockCount) + { + memoryBlocksSpan = new(newMemoryBlocks, sourceBlockCount, newMemoryBlocks.Length - sourceBlockCount); + if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) + { + if (sourceBlockCount == 0) + { + memoryBlocksSpan[0] = blockArrayPool.Rent(minBlockSize); + minBlockSize = memoryBlocksSpan[0].Length; + blockIndex = 1; + } + else + { + blockIndex = 0; + } + + while (blockIndex < memoryBlocksSpan.Length) + { + memoryBlocksSpan[blockIndex++] = blockArrayPool.Rent(minBlockSize); + } + } + else + { + blockIndex = 0; + while (blockIndex < memoryBlocksSpan.Length) + { + memoryBlocksSpan[blockIndex++] = new T[minBlockSize]; + } + } + } + + return newMemoryBlocks.Length << minBlockSizePowerToShift; + } + + internal long EnsureCapacity(long requestedCapacity) + { + long newCapacity = _capacity > 0 + ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) + : requestedCapacity; + + newCapacity = Resize(ref _memoryBlocks!, _blockSize, _capacity, newCapacity, _memoryBlocksPool, _blockArrayPool); + if (newCapacity > 0 && _capacity == 0) + { + _blockSize = _memoryBlocks[0].Length; + _blockSizePow2Shift = MathUtils.GetPow2Shift(_blockSize); + } + + _capacity = newCapacity; + return newCapacity; + } + + public OldRecyclableList(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + { + memoryBlocksPool ??= _defaultMemoryBlocksPool; + blockArrayPool ??= _defaultBlockArrayPool; + _memoryBlocksPool = memoryBlocksPool; + _blockArrayPool = blockArrayPool; + + if (expectedItemsCount > 0) + { + _capacity = Resize(ref _memoryBlocks!, (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize), 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); + if (_memoryBlocks.Length > 0) + { + minBlockSize = _memoryBlocks[0].Length; + } + + _blockSize = minBlockSize; + _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); + } + else + { + _blockSize = (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize); + _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); + _memoryBlocks = _emptyMemoryBlocksArray; + } + } + + public OldRecyclableList(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + { + memoryBlocksPool ??= _defaultMemoryBlocksPool; + blockArrayPool ??= _defaultBlockArrayPool; + _memoryBlocksPool = memoryBlocksPool; + _blockArrayPool = blockArrayPool; + + if (expectedItemsCount > 0) + { + _capacity = Resize(ref _memoryBlocks!, (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize), 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); + if (_memoryBlocks.Length > 0) + { + minBlockSize = _memoryBlocks[0].Length; + } + + _blockSize = minBlockSize; + _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); + } + else + { + _blockSize = (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize); + _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); + _memoryBlocks = _emptyMemoryBlocksArray; + } + + AddRange(source); + } + + public T this[long index] + { + get => _memoryBlocks[index >> _blockSizePow2Shift][index & (_blockSize - 1)]; + set => new Span(_memoryBlocks[(int)(index >> _blockSizePow2Shift)])[(int)(index & (_blockSize - 1))] = value; + } + + public T this[int index] + { + get => _memoryBlocks[index >> _blockSizePow2Shift][index & (_blockSize - 1)]; + set => new Span(_memoryBlocks[index >> _blockSizePow2Shift])[index & (_blockSize - 1)] = value; + } + + public T[][] MemoryBlocks { get => _memoryBlocks; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(T item) + { + if (_capacity < _longCount + 1) + { + _ = EnsureCapacity(_longCount + 1); + } + + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex++] = item; + if (_nextItemIndex == _blockSize) + { + _nextItemBlockIndex++; + _nextItemIndex = 0; + } + + _longCount++; + } + + public void AddRange(in T[] items) + { + if (items.LongLength == 0) + { + return; + } + + long targetCapacity = _longCount + items.LongLength; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _memoryBlocks.Length; + + Span itemsSpan = new(items); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + if (itemsSpan.Length > 0) + { + itemsSpan.CopyTo(targetBlockArraySpan); + } + + _nextItemIndex = itemsSpan.Length; + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableArrayList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _memoryBlocks.Length; + + Span itemsSpan = new(items.AsArray(), 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + _nextItemIndex = itemsSpan.Length; + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(OldRecyclableList items) + { + if (items.LongCount == 0) + { + return; + } + + long itemsCount = items.LongCount, + targetCapacity = _longCount + itemsCount; + + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + int blockSize = _blockSize, + sourceBlockIndex = 0, + targetBlockIndex = _nextItemBlockIndex, + toCopy; + + Span sourceMemoryBlocksSpan = new(items._memoryBlocks, 0, items._memoryBlocks.Length); + Span itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + Span targetMemoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); + Span targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + long copiedCount = 0; + while (copiedCount < itemsCount) + { + toCopy = (int)Math.Min(itemsCount - copiedCount, itemsSpan.Length); + if (targetBlockArraySpan.Length < toCopy) + { + toCopy = targetBlockArraySpan.Length; + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + itemsSpan = itemsSpan[toCopy..]; + copiedCount += toCopy; + } + else + { + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockArraySpan = targetBlockArraySpan[toCopy..]; + itemsSpan = itemsSpan[toCopy..]; + if (itemsSpan.IsEmpty && sourceBlockIndex + 1 < sourceMemoryBlocksSpan.Length) + { + sourceBlockIndex++; + itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + } + + copiedCount += toCopy; + if (targetBlockArraySpan.IsEmpty && copiedCount < itemsCount) + { + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + } + } + + if ((targetCapacity & (blockSize - 1)) == 0) + { + _nextItemIndex = 0; + _nextItemBlockIndex = targetBlockIndex + 1; + } + else + { + _nextItemIndex = (int)(targetCapacity & (blockSize - 1)); + _nextItemBlockIndex = targetBlockIndex; + + } + + _longCount = targetCapacity; + } + + public void AddRange(List items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + itemsCount = items.Count, + copied = Math.Min(blockSize - _nextItemIndex, itemsCount); + + Span memoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); + items.CopyTo(0, memoryBlocksSpan[targetBlockIndex], _nextItemIndex, copied); + if (_nextItemIndex + copied == blockSize) + { + targetBlockIndex++; + _nextItemIndex = 0; + } + else + { + _nextItemIndex += copied; + } + + while (blockSize <= itemsCount - copied) + { + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex++], 0, blockSize); + copied += blockSize; + } + + if ((itemsCount - copied < blockSize) && (itemsCount - copied != 0)) + { + _nextItemIndex = itemsCount - copied; + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex], 0, _nextItemIndex); + } + + _nextItemBlockIndex = targetBlockIndex; + _longCount = targetCapacity; + } + + public void AddRange(IList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _memoryBlocks.Length; + + T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? _blockArrayPool.Rent(items.Count) : new T[items.Count]; + try + { + items.CopyTo(itemsBuffer, 0); + + Span itemsSpan = new(itemsBuffer, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + _nextItemIndex = itemsSpan.Length; + } + finally + { + if (items.Count >= RecyclableDefaults.MinPooledArrayLength) + { + _blockArrayPool.Return(itemsBuffer, NeedsClearing); + } + } + } + + public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) + { + if (source is OldRecyclableList sourceRecyclableList) + { + AddRange(sourceRecyclableList); + return; + } + + if (source is RecyclableArrayList sourceRecyclableArrayList) + { + AddRange(sourceRecyclableArrayList); + return; + } + + if (source is T[] sourceArray) + { + AddRange(sourceArray); + return; + } + + if (source is List sourceList) + { + AddRange(sourceList); + return; + } + + if (source is IList sourceIList) + { + AddRange(sourceIList); + return; + } + + if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) + { + AddRangeWithKnownCount(source, requiredAdditionalCapacity); + return; + } + + AddRangeEnumerated(source, growByCount); + } + + public void Clear() + { + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); + int memoryBlocksCount = memoryBlocksSpan.Length; + ArrayPool blockArrayPool = _blockArrayPool; + for (int toRemoveIdx = 0; toRemoveIdx < memoryBlocksCount; toRemoveIdx++) + { + blockArrayPool.Return(memoryBlocksSpan[toRemoveIdx], NeedsClearing); + } + } + + _capacity = 0; + _nextItemBlockIndex = 0; + _nextItemIndex = 0; + _longCount = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(T item) + { + if (_longCount == 0) + { + return false; + } + + if (_nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0)) + { + return Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) >= 0; + } + + Span memoryBlocksSpan = new(_memoryBlocks); + int lastBlockIndex = _nextItemBlockIndex; + for (int blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + if (Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _blockSize) >= 0) + { + return true; + } + } + + return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, _nextItemIndex) >= 0; + } + + public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, arrayIndex); + + public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndex + : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) + ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) + : (int)DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex); + + public void Insert(int index, T item) => throw new NotSupportedException(); + public long LongIndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndex + : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) + ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) + : DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex); + + public bool Remove(T item) + { + var itemIndex = LongIndexOf(item); + if (itemIndex >= 0) + { + DoRemoveAt(itemIndex); + return true; + } + + return false; + } + + public void RemoveBlock(int index) + { + if (_blockSize < RecyclableDefaults.MinPooledArrayLength) + { + _memoryBlocks[index] = _emptyBlockArray; + } + else + { + _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); + } + + _capacity -= _blockSize; + if (_nextItemBlockIndex == index) + { + _nextItemIndex = 0; + } + else if (_nextItemBlockIndex > index) + { + _nextItemBlockIndex--; + } + } + + public void RemoveAt(int index) + { + if (index > _longCount - 1 || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + DoRemoveAt(index); + } + + public void RemoveAt(long index) + { + if (index > _longCount - 1 || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + DoRemoveAt(index); + } + + IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + ~OldRecyclableList() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + if (_capacity > 0) + { + Clear(); + if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength) + { + _memoryBlocksPool.Return(_memoryBlocks, NeedsClearing); + } + + GC.SuppressFinalize(this); + } + } + } + + + #endregion + + [Benchmark(Baseline = true)] + public void RecyclableList_EnsureCapacity_Old_ByPowOf2() + { + using var list = new OldRecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + _ = list.EnsureCapacity(TestObjectCount); + } + + [Benchmark(Baseline = false)] + public void RecyclableList_EnsureCapacity_ByPowOf2() + { + using var list = new RecyclableList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + _ = RecyclableList.EnsureCapacity(list, TestObjectCount); + } + + [Benchmark(Baseline = false)] + public void RecyclableList_EnsureCapacity_Old_ByBlockSize() + { + using var list = new OldRecyclableList(); + _ = list.EnsureCapacity(TestObjectCount); + } + + [Benchmark(Baseline = false)] + public void RecyclableList_EnsureCapacity_ByBlockSize() + { + using var list = new RecyclableList(); + _ = RecyclableList.EnsureCapacity(list, TestObjectCount); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs new file mode 100644 index 0000000..738c0ea --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableListPoc.Benchmarks.cs @@ -0,0 +1,14 @@ +namespace Recyclable.Collections.Benchmarks.POC +{ + public partial class RecyclableListPocBenchmarks : PocBenchmarkBase + { + public static void Run() + { + var benchmark = new RecyclableListPocBenchmarks(); + benchmark.RecyclableList_EnsureCapacity_Old_ByPowOf2(); + benchmark.RecyclableList_EnsureCapacity_ByPowOf2(); + benchmark.RecyclableList_EnsureCapacity_Old_ByBlockSize(); + benchmark.RecyclableList_EnsureCapacity_ByBlockSize(); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/POC/RefVsInstanceMemberPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/RefVsInstanceMemberPoc.Benchmarks.cs new file mode 100644 index 0000000..3c9c429 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/RefVsInstanceMemberPoc.Benchmarks.cs @@ -0,0 +1,362 @@ +using BenchmarkDotNet.Attributes; + +namespace Recyclable.Collections.Benchmarks.POC +{ + public class RefVsInstanceMemberPocBenchmarks + { + public static void Run() + { + var benchmark = new RefVsInstanceMemberPocBenchmarks(); + benchmark.ArrayInstanceMember2(); + benchmark.ArrayInstanceMember3(); + benchmark.ArrayInstanceMember4(); + benchmark.ArrayInstanceMember5(); + benchmark.ArrayInstanceMember6(); + benchmark.ArrayInstanceMemberLocalVar5(); + benchmark.ArrayInstanceMemberLocalVar6(); + benchmark.IntInstanceMember2(); + benchmark.IntInstanceMember3(); + benchmark.IntInstanceMember4(); + benchmark.IntInstanceMember5(); + benchmark.RefToArray2(); + benchmark.RefToArray3(); + benchmark.RefToArray4(); + benchmark.RefToArray5(); + benchmark.RefToArray6(); + benchmark.RefToInt2(); + benchmark.RefToInt3(); + } + + private readonly TestCase _testCase = new(); + private static void DoNothing(int count) + { + _ = count; + } + + private class TestCase + { + private int[] _items = new int[123]; + private int _count = 123; + private static int Add(int count) + { + return count; + } + + public int Count + { + get => _count; + set => _count = value; + } + + public ref int GetCountAsRefCount2() + { + ref int count = ref _count; + count = 234; + return ref count; + } + + public ref int GetCountAsRefCount3() + { + ref int count = ref _count; + count = 234; + _ = TestCase.Add(count); + return ref count; + } + + public ref int GetCountAsRefCount4() + { + ref int count = ref _count; + count = 234; + _ = TestCase.Add(count); + _ = TestCase.Add(count); + return ref count; + } + + public ref int GetCountAsRefCount5() + { + ref int count = ref _count; + count = 234; + _ = TestCase.Add(count); + _ = TestCase.Add(count); + _ = TestCase.Add(count); + return ref count; + } + + public int GetCountAsInstanceMember2() + { + _count = 234; + return _count; + } + + public int GetCountAsInstanceMember3() + { + _count = 234; + _ = TestCase.Add(_count); + return _count; + } + + public int GetCountAsInstanceMember4() + { + _count = 234; + _ = TestCase.Add(_count); + _ = TestCase.Add(_count); + return _count; + } + + public int GetCountAsInstanceMember5() + { + _count = 234; + _ = TestCase.Add(_count); + _ = TestCase.Add(_count); + _ = TestCase.Add(_count); + return _count; + } + + public int GetLengthAsArray2() + { + _ = TestCase.Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArray3() + { + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArray4() + { + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArray5() + { + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArray6() + { + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArrayLocalVar5() + { + var items = _items; + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + return items.Length; + } + + public int GetLengthAsArrayLocalVar6() + { + var items = _items; + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray2() + { + ref int[] items = ref _items; + _ = TestCase.Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray3() + { + ref int[] items = ref _items; + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray4() + { + ref int[] items = ref _items; + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray5() + { + ref int[] items = ref _items; + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray6() + { + ref int[] items = ref _items; + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + return items.Length; + } + } + + [Benchmark] + public void RefToInt2() + { + var count = _testCase.GetCountAsRefCount2(); + DoNothing(count); + } + + [Benchmark] + public void RefToInt3() + { + var count = _testCase.GetCountAsRefCount3(); + DoNothing(count); + } + + [Benchmark] + public void RefToInt4() + { + var count = _testCase.GetCountAsRefCount4(); + DoNothing(count); + } + + [Benchmark] + public void RefToInt5() + { + var count = _testCase.GetCountAsRefCount5(); + DoNothing(count); + } + + [Benchmark(Baseline = true)] + public void IntInstanceMember2() + { + var count = _testCase.GetCountAsInstanceMember2(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void IntInstanceMember3() + { + var count = _testCase.GetCountAsInstanceMember3(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void IntInstanceMember4() + { + var count = _testCase.GetCountAsInstanceMember4(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void IntInstanceMember5() + { + var count = _testCase.GetCountAsInstanceMember5(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray2() + { + var count = _testCase.GetLengthAsRefArray2(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray3() + { + var count = _testCase.GetLengthAsRefArray3(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray4() + { + var count = _testCase.GetLengthAsRefArray4(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray5() + { + var count = _testCase.GetLengthAsRefArray5(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray6() + { + var count = _testCase.GetLengthAsRefArray6(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember2() + { + var count = _testCase.GetLengthAsArray2(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember3() + { + var count = _testCase.GetLengthAsArray3(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember4() + { + var count = _testCase.GetLengthAsArray4(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember5() + { + var count = _testCase.GetLengthAsArray5(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember6() + { + var count = _testCase.GetLengthAsArray6(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMemberLocalVar5() + { + var count = _testCase.GetLengthAsArrayLocalVar5(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMemberLocalVar6() + { + var count = _testCase.GetLengthAsArrayLocalVar6(); + DoNothing(count); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index 8ff8914..d7a156f 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -1,100 +1,54 @@ using BenchmarkDotNet.Configs; using BenchmarkDotNet.Running; using Recyclable.Collections.Benchmarks; - -//BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(new[] { "-f*" }); - -//BenchmarkRunner.Run(); - - +using Recyclable.Collections.Benchmarks.POC; internal class Program { - static void RunModuloBenchmarks() + static IConfig BenchmarkConfig { get; } = ManualConfig.Create(DefaultConfig.Instance).WithOptions(ConfigOptions.DisableOptimizationsValidator | ConfigOptions.JoinSummary); + + static void RunAssemblyBenchmarks() { - var benchmark = new ModuloBenchmarks(); - //benchmark.AddWithFor(); - //benchmark.DividerAndModulusOperator(); - //benchmark.MathDivRem(); - //benchmark.SubtracWithWhile(); - //benchmark.XDivYMulX(); - //benchmark.XDivYMulXWithLocalVar(); - //benchmark.AddWithForOptimized(); - //benchmark.MixedApproachWithForLoopOptimized(); - //benchmark.MixedApproachWithSubtractWithWhile(); - benchmark.MixedApproachWithSubtractWithWhileAsVector(); + _ = BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(new[] { "-f*" }); } - static void RunBenchmark() + private static void RunRecyclableCollectionsBenchmarks() { - var benchmark = new RecyclableCollectionsBenchmarks(); - benchmark.Setup(); + _ = BenchmarkRunner.Run(BenchmarkConfig); + } + + static void RunSelectedBenchmarks() + { + // **************** + // *** Template *** + // **************** + //var benchmark = new RecyclableCollectionsBenchmarks(); + //benchmark.Setup(); + //foreach (var _ in Enumerable.Range(1, 1000)) //{ - //benchmark.RecyclableList_Contains(); - //benchmark.RecyclableArrayList_AddRange_WithCapacity(); - //benchmark.RecyclableArrayList_Add_WithCapacity(); - - //benchmark.RecyclableList_Create(); - //benchmark.RecyclableList_Create_WithCapacity(); - //benchmark.RecyclableList_Add(); - //benchmark.RecyclableList_Add_WithCapacity(); - //benchmark.RecyclableList_Contains_LastItems(); - //benchmark.RecyclableList_IndexOf_FirstItems(); - //benchmark.RecyclableList_AddRangeWhenSourceIsList(); //benchmark.RecyclableList_Remove_FirstItems(); //benchmark.RecyclableList_RemoveAt_LastItems(); - //benchmark.RecyclableList_EnsureCapacity_New_ByBlockSize(); - benchmark.RecyclableList_EnsureCapacity_ByPowOf2(); //benchmark.List_RemoveAt_LastItems(); //} - benchmark.Cleanup(); + //benchmark.Cleanup(); + } + + static void RunPocBenchmarks() + { + _ = BenchmarkRunner.Run(BenchmarkConfig); + _ = BenchmarkRunner.Run(BenchmarkConfig); + _ = BenchmarkRunner.Run(BenchmarkConfig); + _ = BenchmarkRunner.Run(BenchmarkConfig); + _ = BenchmarkRunner.Run(BenchmarkConfig); } private static void Main(string[] args) { - BenchmarkRunner.Run( - ManualConfig.Create(DefaultConfig.Instance).WithOptions(ConfigOptions.DisableOptimizationsValidator | ConfigOptions.JoinSummary) - ); - - //var pool = ArrayPool.Shared; - //for (long bufferSize = int.MaxValue / 2; bufferSize <= int.MaxValue; bufferSize += 100_000_000) - //{ - // try - // { - // Console.WriteLine($"Allocating {bufferSize}"); - // var arr = pool.Rent((int)bufferSize); - // pool.Return(arr); - // GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); - // } - // catch (Exception) - // { - // Console.WriteLine($"Error during array creation. {nameof(bufferSize)} = {bufferSize}"); - // throw; - // } - //} - - //RunBenchmark(); - - - //BenchmarkRunner.Run( - // ManualConfig.Create(DefaultConfig.Instance).WithOptions(ConfigOptions.DisableOptimizationsValidator | ConfigOptions.JoinSummary) - //); - - //RunModuloBenchmarks(); + RunRecyclableCollectionsBenchmarks(); + //RunPocBenchmarks(); + //RunSelectedBenchmarks(); + //RunAssemblyBenchmarks(); } } -// benchmark.Setup(); -// benchmark.Cleanup(); - -//benchmark.Setup(); -//benchmark.Cleanup(); - -//} - - -//BenchmarkRunner.Run(); - -//var tests = new ListVsLinkedListBenchmark(); -//tests.RecyclableList(); diff --git a/Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs b/Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs deleted file mode 100644 index cd21368..0000000 --- a/Recyclable.Collections.Benchmarks/RefVsInstanceMemberBenchmarks.cs +++ /dev/null @@ -1,339 +0,0 @@ -using BenchmarkDotNet.Attributes; - -namespace Recyclable.Collections.Benchmarks -{ - public class RefVsInstanceMemberBenchmarks - { - private readonly TestCase _testCase = new(); - private void DoNothing(int count) - { - _ = count; - } - - private class TestCase - { - private int[] _items = new int[123]; - private int _count = 123; - private int Add(int count) - { - return count; - } - - public int Count - { - get => _count; - set => _count = value; - } - - public ref int GetCountAsRefCount2() - { - ref int count = ref _count; - count = 234; - return ref count; - } - - public ref int GetCountAsRefCount3() - { - ref int count = ref _count; - count = 234; - _ = Add(count); - return ref count; - } - - public ref int GetCountAsRefCount4() - { - ref int count = ref _count; - count = 234; - _ = Add(count); - _ = Add(count); - return ref count; - } - - public ref int GetCountAsRefCount5() - { - ref int count = ref _count; - count = 234; - _ = Add(count); - _ = Add(count); - _ = Add(count); - return ref count; - } - - public int GetCountAsInstanceMember2() - { - _count = 234; - return _count; - } - - public int GetCountAsInstanceMember3() - { - _count = 234; - _ = Add(_count); - return _count; - } - - public int GetCountAsInstanceMember4() - { - _count = 234; - _ = Add(_count); - _ = Add(_count); - return _count; - } - - public int GetCountAsInstanceMember5() - { - _count = 234; - _ = Add(_count); - _ = Add(_count); - _ = Add(_count); - return _count; - } - - public int GetLengthAsArray2() - { - _ = Add(_items.Length); - return _items.Length; - } - - public int GetLengthAsArray3() - { - _ = Add(_items.Length); - _ = Add(_items.Length); - return _items.Length; - } - - public int GetLengthAsArray4() - { - _ = Add(_items.Length); - _ = Add(_items.Length); - _ = Add(_items.Length); - return _items.Length; - } - - public int GetLengthAsArray5() - { - _ = Add(_items.Length); - _ = Add(_items.Length); - _ = Add(_items.Length); - _ = Add(_items.Length); - return _items.Length; - } - - public int GetLengthAsArray6() - { - _ = Add(_items.Length); - _ = Add(_items.Length); - _ = Add(_items.Length); - _ = Add(_items.Length); - _ = Add(_items.Length); - return _items.Length; - } - - public int GetLengthAsArrayLocalVar5() - { - var items = _items; - _ = Add(items.Length); - _ = Add(items.Length); - _ = Add(items.Length); - _ = Add(items.Length); - return items.Length; - } - - public int GetLengthAsArrayLocalVar6() - { - var items = _items; - _ = Add(items.Length); - _ = Add(items.Length); - _ = Add(items.Length); - _ = Add(items.Length); - _ = Add(items.Length); - return items.Length; - } - - public int GetLengthAsRefArray2() - { - ref int[] items = ref _items; - _ = Add(items.Length); - return items.Length; - } - - public int GetLengthAsRefArray3() - { - ref int[] items = ref _items; - _ = Add(items.Length); - _ = Add(items.Length); - return items.Length; - } - - public int GetLengthAsRefArray4() - { - ref int[] items = ref _items; - _ = Add(items.Length); - _ = Add(items.Length); - _ = Add(items.Length); - return items.Length; - } - - public int GetLengthAsRefArray5() - { - ref int[] items = ref _items; - _ = Add(items.Length); - _ = Add(items.Length); - _ = Add(items.Length); - _ = Add(items.Length); - return items.Length; - } - - public int GetLengthAsRefArray6() - { - ref int[] items = ref _items; - _ = Add(items.Length); - _ = Add(items.Length); - _ = Add(items.Length); - _ = Add(items.Length); - _ = Add(items.Length); - return items.Length; - } - } - - [Benchmark] - public void RefToInt2() - { - var count = _testCase.GetCountAsRefCount2(); - DoNothing(count); - } - - [Benchmark] - public void RefToInt3() - { - var count = _testCase.GetCountAsRefCount3(); - DoNothing(count); - } - - [Benchmark] - public void RefToInt4() - { - var count = _testCase.GetCountAsRefCount4(); - DoNothing(count); - } - - [Benchmark] - public void RefToInt5() - { - var count = _testCase.GetCountAsRefCount5(); - DoNothing(count); - } - - [Benchmark(Baseline = true)] - public void IntInstanceMember2() - { - var count = _testCase.GetCountAsInstanceMember2(); - DoNothing(count); - } - - [Benchmark(Baseline = false)] - public void IntInstanceMember3() - { - var count = _testCase.GetCountAsInstanceMember3(); - DoNothing(count); - } - - [Benchmark(Baseline = false)] - public void IntInstanceMember4() - { - var count = _testCase.GetCountAsInstanceMember4(); - DoNothing(count); - } - - [Benchmark(Baseline = false)] - public void IntInstanceMember5() - { - var count = _testCase.GetCountAsInstanceMember5(); - DoNothing(count); - } - - [Benchmark] - public void RefToArray2() - { - var count = _testCase.GetLengthAsRefArray2(); - DoNothing(count); - } - - [Benchmark] - public void RefToArray3() - { - var count = _testCase.GetLengthAsRefArray3(); - DoNothing(count); - } - - [Benchmark] - public void RefToArray4() - { - var count = _testCase.GetLengthAsRefArray4(); - DoNothing(count); - } - - [Benchmark] - public void RefToArray5() - { - var count = _testCase.GetLengthAsRefArray5(); - DoNothing(count); - } - - [Benchmark] - public void RefToArray6() - { - var count = _testCase.GetLengthAsRefArray6(); - DoNothing(count); - } - - [Benchmark(Baseline = false)] - public void ArrayInstanceMember2() - { - var count = _testCase.GetLengthAsArray2(); - DoNothing(count); - } - - [Benchmark(Baseline = false)] - public void ArrayInstanceMember3() - { - var count = _testCase.GetLengthAsArray3(); - DoNothing(count); - } - - [Benchmark(Baseline = false)] - public void ArrayInstanceMember4() - { - var count = _testCase.GetLengthAsArray4(); - DoNothing(count); - } - - [Benchmark(Baseline = false)] - public void ArrayInstanceMember5() - { - var count = _testCase.GetLengthAsArray5(); - DoNothing(count); - } - - [Benchmark(Baseline = false)] - public void ArrayInstanceMember6() - { - var count = _testCase.GetLengthAsArray6(); - DoNothing(count); - } - - [Benchmark(Baseline = false)] - public void ArrayInstanceMemberLocalVar5() - { - var count = _testCase.GetLengthAsArrayLocalVar5(); - DoNothing(count); - } - - [Benchmark(Baseline = false)] - public void ArrayInstanceMemberLocalVar6() - { - var count = _testCase.GetLengthAsArrayLocalVar6(); - DoNothing(count); - } - } -}