diff --git a/Build/Common.Benchmark.props b/Build/Common.Benchmark.props index fa7ca0aa..f6e225c0 100644 --- a/Build/Common.Benchmark.props +++ b/Build/Common.Benchmark.props @@ -1,6 +1,6 @@ - netcoreapp3.1;net48;net50 + net50;netcoreapp3.1;net48 diff --git a/Documents/BenchmarksResults/SelectOnArray.md b/Documents/BenchmarksResults/SelectOnArray.md new file mode 100644 index 00000000..a93161ec --- /dev/null +++ b/Documents/BenchmarksResults/SelectOnArray.md @@ -0,0 +1,22 @@ +## SelectOnArray + +### Source +[SelectOnArray.cs](../../src/StructLinq.Benchmark/SelectOnArray.cs) + +### Results: +``` ini + +BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042 +Intel Core i7-7700 CPU 3.60GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores +.NET Core SDK=5.0.101 + [Host] : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT + DefaultJob : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT + + +``` +| Method | Mean | Error | StdDev | Ratio | RatioSD | Code Size | Gen 0 | Gen 1 | Gen 2 | Allocated | +|----------------------- |---------:|---------:|---------:|------:|--------:|----------:|------:|------:|------:|----------:| +| Handmaded | 11.27 μs | 0.011 μs | 0.009 μs | 1.00 | 0.00 | 53 B | - | - | - | - | +| LINQ | 61.02 μs | 0.356 μs | 0.298 μs | 5.41 | 0.03 | 1147 B | - | - | - | 48 B | +| StructLINQ | 20.85 μs | 0.098 μs | 0.087 μs | 1.85 | 0.01 | 627 B | - | - | - | - | +| StructLINQWithFunction | 17.78 μs | 0.076 μs | 0.063 μs | 1.58 | 0.01 | 593 B | - | - | - | - | diff --git a/Documents/BenchmarksResults/SelectOnArrayOfClass.md b/Documents/BenchmarksResults/SelectOnArrayOfClass.md new file mode 100644 index 00000000..afd7ebf7 --- /dev/null +++ b/Documents/BenchmarksResults/SelectOnArrayOfClass.md @@ -0,0 +1,22 @@ +## SelectOnArrayOfClass + +### Source +[SelectOnArrayOfClass.cs](../../src/StructLinq.Benchmark/SelectOnArrayOfClass.cs) + +### Results: +``` ini + +BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042 +Intel Core i7-7700 CPU 3.60GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores +.NET Core SDK=5.0.101 + [Host] : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT + DefaultJob : .NET Core 5.0.1 (CoreCLR 5.0.120.57516, CoreFX 5.0.120.57516), X64 RyuJIT + + +``` +| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | Code Size | +|----------------------- |----------:|----------:|----------:|------:|--------:|------:|------:|------:|----------:|----------:| +| Handmaded | 6.726 μs | 0.1298 μs | 0.1494 μs | 1.00 | 0.00 | - | - | - | - | 48 B | +| LINQ | 76.179 μs | 1.4866 μs | 1.6523 μs | 11.32 | 0.33 | - | - | - | 48 B | 48 B | +| StructLINQ | 27.129 μs | 0.4152 μs | 0.3467 μs | 4.04 | 0.10 | - | - | - | - | 48 B | +| StructLINQWithFunction | 17.780 μs | 0.2278 μs | 0.2020 μs | 2.64 | 0.07 | - | - | - | - | 48 B | diff --git a/src/StructLinq.Benchmark/ArrayOfClassSum.cs b/src/StructLinq.Benchmark/ArrayOfClassSum.cs index 3df7e92c..05b62a29 100644 --- a/src/StructLinq.Benchmark/ArrayOfClassSum.cs +++ b/src/StructLinq.Benchmark/ArrayOfClassSum.cs @@ -1,4 +1,5 @@ using System.Linq; +using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; namespace StructLinq.Benchmark @@ -49,6 +50,7 @@ public Container(int element) internal struct ContainerSelect : IFunction { + [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly int Eval(Container element) { return element.Element; diff --git a/src/StructLinq.Benchmark/SelectOnArray.cs b/src/StructLinq.Benchmark/SelectOnArray.cs new file mode 100644 index 00000000..8bde2fc2 --- /dev/null +++ b/src/StructLinq.Benchmark/SelectOnArray.cs @@ -0,0 +1,67 @@ +using System.Linq; +using BenchmarkDotNet.Attributes; + +namespace StructLinq.Benchmark +{ + [MemoryDiagnoser, DisassemblyDiagnoser(4)] + public class SelectOnArray + { + private int[] array; + private const int Count = 10000; + + public SelectOnArray() + { + array = Enumerable.Range(0, Count).ToArray(); + } + + [Benchmark(Baseline = true)] + public double Handmaded() + { + var sum = 0.0; + foreach (var i in array) + { + var x = i * 2.0; + sum += x; + } + + return sum; + } + + [Benchmark] + public double LINQ() + { + var sum = 0.0; + foreach (var i in array.Select(x=> x * 2.0)) + { + sum += i; + } + + return sum; + } + + [Benchmark] + public double StructLINQ() + { + var sum = 0.0; + foreach (var i in array.ToStructEnumerable().Select(x=> x * 2.0, x=>x)) + { + sum += i; + } + + return sum; + } + + [Benchmark] + public double StructLINQWithFunction() + { + var sum = 0.0; + var func = new MultFunction(); + foreach (var i in array.ToStructEnumerable().Select(ref func, x=>x, x=> x)) + { + sum += i; + } + + return sum; + } + } +} diff --git a/src/StructLinq.Benchmark/SelectOnArrayOfClass.cs b/src/StructLinq.Benchmark/SelectOnArrayOfClass.cs new file mode 100644 index 00000000..330434c6 --- /dev/null +++ b/src/StructLinq.Benchmark/SelectOnArrayOfClass.cs @@ -0,0 +1,66 @@ +using System.Linq; +using BenchmarkDotNet.Attributes; + +namespace StructLinq.Benchmark +{ + [MemoryDiagnoser, DisassemblyDiagnoser(4)] + public class SelectOnArrayOfClass + { + private Container[] array; + private const int Count = 10000; + + public SelectOnArrayOfClass() + { + array = Enumerable.Range(0, Count).Select(x=> new Container(x)).ToArray(); + } + + [Benchmark(Baseline = true)] + public int Handmaded() + { + var sum = 0; + foreach (var i in array) + { + sum += i.Element; + } + + return sum; + } + + [Benchmark] + public int LINQ() + { + var sum = 0; + foreach (var i in array.Select(x=> x.Element)) + { + sum += i; + } + + return sum; + } + + [Benchmark] + public int StructLINQ() + { + var sum = 0; + foreach (var i in array.ToStructEnumerable().Select(x=> x.Element, x=>x)) + { + sum += i; + } + + return sum; + } + + [Benchmark] + public int StructLINQWithFunction() + { + var sum = 0; + var func = new ContainerSelect(); + foreach (var i in array.ToStructEnumerable().Select(ref func, x=>x, x=> x)) + { + sum += i; + } + + return sum; + } + } +} diff --git a/src/StructLinq.Tests/SelectFuncTests.cs b/src/StructLinq.Tests/SelectFuncTests.cs new file mode 100644 index 00000000..91f3b6a6 --- /dev/null +++ b/src/StructLinq.Tests/SelectFuncTests.cs @@ -0,0 +1,37 @@ +using System; +using System.Linq; +using StructLinq.Range; +using StructLinq.Select; +using Xunit; + +namespace StructLinq.Tests +{ + public class SelectFuncTests : AbstractEnumerableTests, + SelectEnumerator> + { + [Fact] + public void DelegateTest() + { + Func selector = x => x * 2.0; + var sys = Enumerable + .Range(-50, 100) + .Select(selector) + .ToArray(); + var structEnum = StructEnumerable + .Range(-50, 100) + .Select(selector) + .ToEnumerable() + .ToArray(); + Assert.Equal(sys, structEnum); + } + + protected override SelectEnumerable Build(int size) + { + var selectEnumerable = + StructEnumerable.Range(-1, size).Select(x=> x * 2.0, x=> x); + return selectEnumerable; + } + + } +} diff --git a/src/StructLinq.Tests/SelectTests.cs b/src/StructLinq.Tests/SelectTests.cs index 0be06214..b1cfeb14 100644 --- a/src/StructLinq.Tests/SelectTests.cs +++ b/src/StructLinq.Tests/SelectTests.cs @@ -6,9 +6,9 @@ namespace StructLinq.Tests { - public class SelectTests : AbstractEnumerableTests, RangeEnumerator, StructFunction>, - SelectEnumerator>> + public class SelectTests : AbstractEnumerableTests, + SelectEnumerator> { [Fact] public void DelegateTest() @@ -18,26 +18,30 @@ public void DelegateTest() .Range(-50, 100) .Select(selector) .ToArray(); + var func = new MultFunction(); var structEnum = StructEnumerable .Range(-50, 100) - .Select(selector) + .Select(ref func, x=>x, x=> x) .ToEnumerable() .ToArray(); Assert.Equal(sys, structEnum); } - protected override SelectEnumerable, RangeEnumerator, StructFunction> Build(int size) + protected override SelectEnumerable Build(int size) { - SelectEnumerable, RangeEnumerator, StructFunction> selectEnumerable = StructEnumerable.Range(-1, size).Select(x => x * 2); + var func = new MultFunction(); + SelectEnumerable selectEnumerable = + StructEnumerable.Range(-1, size).Select(ref func, x=>x, x => x); return selectEnumerable; } - struct MultFunction : IFunction + } + + public struct MultFunction : IFunction + { + public double Eval(int element) { - public double Eval(int element) - { - return element * 2.0; - } + return element * 2.0; } } } diff --git a/src/StructLinq.Tests/ZeroAllocSelectTests.cs b/src/StructLinq.Tests/ZeroAllocSelectTests.cs deleted file mode 100644 index e1951db3..00000000 --- a/src/StructLinq.Tests/ZeroAllocSelectTests.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Linq; -using StructLinq.Range; -using StructLinq.Select; -using Xunit; - -namespace StructLinq.Tests -{ - public class ZeroAllocSelectTests : AbstractEnumerableTests>, - SelectEnumerator>> - { - [Fact] - public void DelegateTest() - { - Func selector = x => x * 2.0; - var sys = Enumerable - .Range(-50, 100) - .Select(selector) - .ToArray(); - var structEnum = StructEnumerable - .Range(-50, 100) - .Select(selector, x=>x) - .ToEnumerable() - .ToArray(); - Assert.Equal(sys, structEnum); - } - - [Fact] - public void StructTest() - { - Func selector = x => x * 2.0; - var sys = Enumerable - .Range(-50, 100) - .Select(selector) - .ToArray(); - var fct = new MultFunction(); - var structEnum = StructEnumerable - .Range(-50, 100) - .Select(ref fct, x=>x, x => x) - .ToEnumerable() - .ToArray(); - Assert.Equal(sys, structEnum); - - } - - protected override SelectEnumerable> Build(int size) - { - var selectEnumerable = StructEnumerable.Range(-1, size).Select(x => x * 2, x=>x); - return selectEnumerable; - } - - struct MultFunction : IFunction - { - public double Eval(int element) - { - return element * 2.0; - } - } - } -} \ No newline at end of file diff --git a/src/StructLinq/Select/SelectEnumerable.cs b/src/StructLinq/Select/SelectEnumerable.cs index 7c618a4d..a05b6f34 100644 --- a/src/StructLinq/Select/SelectEnumerable.cs +++ b/src/StructLinq/Select/SelectEnumerable.cs @@ -1,4 +1,5 @@ -using System.Runtime.CompilerServices; +using System; +using System.Runtime.CompilerServices; namespace StructLinq.Select { @@ -31,6 +32,36 @@ internal TEnumerable Inner get => inner; } } + + public struct SelectEnumerable : IStructEnumerable> + where TEnumerator : struct, IStructEnumerator + where TEnumerable : IStructEnumerable + { + #region private fields + private Func function; + private TEnumerable inner; + #endregion + + public SelectEnumerable(Func function, ref TEnumerable inner) + { + this.function = function; + this.inner = inner; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public SelectEnumerator GetEnumerator() + { + var typedEnumerator = inner.GetEnumerator(); + return new SelectEnumerator(function, ref typedEnumerator); + } + + internal TEnumerable Inner + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => inner; + } + } + } diff --git a/src/StructLinq/Select/SelectEnumerator.cs b/src/StructLinq/Select/SelectEnumerator.cs index 8d6a9262..32a9d776 100644 --- a/src/StructLinq/Select/SelectEnumerator.cs +++ b/src/StructLinq/Select/SelectEnumerator.cs @@ -1,4 +1,5 @@ -using System.Runtime.CompilerServices; +using System; +using System.Runtime.CompilerServices; namespace StructLinq.Select { @@ -38,4 +39,40 @@ public void Dispose() } } + + public struct SelectEnumerator : IStructEnumerator + where TEnumerator : struct, IStructEnumerator + { + #region private fields + private Func function; + private TEnumerator enumerator; + #endregion + public SelectEnumerator(Func function, ref TEnumerator enumerator) + { + this.function = function; + this.enumerator = enumerator; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() + { + return enumerator.MoveNext(); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Reset() + { + enumerator.Reset(); + } + public TOut Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => function(enumerator.Current); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() + { + enumerator.Dispose(); + } + + } } \ No newline at end of file diff --git a/src/StructLinq/Select/StructEnumerable.Select.cs b/src/StructLinq/Select/StructEnumerable.Select.cs index 6c0b6981..b4c6846a 100644 --- a/src/StructLinq/Select/StructEnumerable.Select.cs +++ b/src/StructLinq/Select/StructEnumerable.Select.cs @@ -18,13 +18,11 @@ public static SelectEnumerable S } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static SelectEnumerable> - Select(this TEnumerable enumerable, Func function, Func> _) + public static SelectEnumerable Select(this TEnumerable enumerable, Func function, Func> _) where TEnumerator : struct, IStructEnumerator where TEnumerable : struct, IStructEnumerable { - var fct = function.ToStruct(); - return new SelectEnumerable>(ref fct, ref enumerable); + return new SelectEnumerable(function, ref enumerable); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -48,19 +46,17 @@ public static RefSelectEnumerable, TEnumerator, StructFunction> Select(this IStructEnumerable enumerable, Func function) + public static SelectEnumerable, TEnumerator> Select(this IStructEnumerable enumerable, Func function) where TEnumerator : struct, IStructEnumerator { - var fct = function.ToStruct(); - return new SelectEnumerable,TEnumerator,StructFunction>(ref fct, ref enumerable); + return new SelectEnumerable,TEnumerator>(function, ref enumerable); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static SelectEnumerable, TEnumerator, StructFunction> Select(this IStructCollection enumerable, Func function) + public static SelectEnumerable, TEnumerator> Select(this IStructCollection enumerable, Func function) where TEnumerator : struct, IStructEnumerator { - var fct = function.ToStruct(); - return new SelectEnumerable,TEnumerator,StructFunction>(ref fct, ref enumerable); + return new SelectEnumerable,TEnumerator>(function, ref enumerable); } [MethodImpl(MethodImplOptions.AggressiveInlining)]