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); } } } }