From 21d222a039de1a857e53e3d04741eff234e41251 Mon Sep 17 00:00:00 2001 From: Reegeek <10356780+reegeek@users.noreply.github.com> Date: Fri, 18 Dec 2020 09:41:47 +0100 Subject: [PATCH 1/2] [Where] Add benchmark --- Documents/BenchmarksResults/WhereOnArray.md | 22 ++++++ .../BenchmarksResults/WhereOnArrayOfClass.md | 22 ++++++ src/StructLinq.Benchmark/WhereOnArray.cs | 77 +++++++++++++++++++ .../WhereOnArrayOfClass.cs | 77 +++++++++++++++++++ 4 files changed, 198 insertions(+) create mode 100644 Documents/BenchmarksResults/WhereOnArray.md create mode 100644 Documents/BenchmarksResults/WhereOnArrayOfClass.md create mode 100644 src/StructLinq.Benchmark/WhereOnArray.cs create mode 100644 src/StructLinq.Benchmark/WhereOnArrayOfClass.cs diff --git a/Documents/BenchmarksResults/WhereOnArray.md b/Documents/BenchmarksResults/WhereOnArray.md new file mode 100644 index 00000000..646eece3 --- /dev/null +++ b/Documents/BenchmarksResults/WhereOnArray.md @@ -0,0 +1,22 @@ +## WhereOnArray + +### Source +[WhereOnArray.cs](../../src/StructLinq.Benchmark/WhereOnArray.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 | 8.121 μs | 0.0320 μs | 0.0300 μs | 1.00 | 0.00 | 58 B | - | - | - | - | +| LINQ | 40.149 μs | 0.1917 μs | 0.1601 μs | 4.94 | 0.02 | 896 B | - | - | - | 48 B | +| StructLINQ | 29.332 μs | 0.2121 μs | 0.1880 μs | 3.61 | 0.03 | 632 B | - | - | - | - | +| StructLINQWithFunction | 16.885 μs | 0.0860 μs | 0.0762 μs | 2.08 | 0.01 | 495 B | - | - | - | - | diff --git a/Documents/BenchmarksResults/WhereOnArrayOfClass.md b/Documents/BenchmarksResults/WhereOnArrayOfClass.md new file mode 100644 index 00000000..804094dd --- /dev/null +++ b/Documents/BenchmarksResults/WhereOnArrayOfClass.md @@ -0,0 +1,22 @@ +## WhereOnArrayOfClass + +### Source +[WhereOnArrayOfClass.cs](../../src/StructLinq.Benchmark/WhereOnArrayOfClass.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 | 9.610 μs | 0.0691 μs | 0.0540 μs | 1.00 | 0.00 | - | - | - | - | 48 B | +| LINQ | 63.090 μs | 1.2080 μs | 1.0088 μs | 6.58 | 0.11 | - | - | - | 48 B | 48 B | +| StructLINQ | 39.239 μs | 0.6532 μs | 0.6110 μs | 4.08 | 0.07 | - | - | - | - | 48 B | +| StructLINQWithFunction | 19.871 μs | 0.1385 μs | 0.1156 μs | 2.07 | 0.02 | - | - | - | - | 494 B | diff --git a/src/StructLinq.Benchmark/WhereOnArray.cs b/src/StructLinq.Benchmark/WhereOnArray.cs new file mode 100644 index 00000000..008052a8 --- /dev/null +++ b/src/StructLinq.Benchmark/WhereOnArray.cs @@ -0,0 +1,77 @@ +using System.Linq; +using System.Runtime.CompilerServices; +using BenchmarkDotNet.Attributes; + +namespace StructLinq.Benchmark +{ + [MemoryDiagnoser, DisassemblyDiagnoser(4)] + public class WhereOnArray + { + private int[] array; + private const int Count = 10000; + + public WhereOnArray() + { + array = Enumerable.Range(0, Count).ToArray(); + } + + [Benchmark(Baseline = true)] + public int Handmaded() + { + var sum = 0; + foreach (var i in array) + { + if (i % 2 == 0) + sum += i; + } + + return sum; + } + + [Benchmark] + public int LINQ() + { + var sum = 0; + foreach (var i in array.Where(x=> x % 2 == 0)) + { + sum += i; + } + + return sum; + } + + [Benchmark] + public int StructLINQ() + { + var sum = 0; + foreach (var i in array.ToStructEnumerable().Where(x=> x % 2 == 0, x=>x)) + { + sum += i; + } + + return sum; + } + + [Benchmark] + public int StructLINQWithFunction() + { + var sum = 0; + var func = new WhereFunc(); + foreach (var i in array.ToStructEnumerable().Where(ref func, x=>x)) + { + sum += i; + } + + return sum; + } + + private readonly struct WhereFunc : IFunction + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Eval(int element) + { + return element % 2 == 0; + } + } + } +} diff --git a/src/StructLinq.Benchmark/WhereOnArrayOfClass.cs b/src/StructLinq.Benchmark/WhereOnArrayOfClass.cs new file mode 100644 index 00000000..bffc26f0 --- /dev/null +++ b/src/StructLinq.Benchmark/WhereOnArrayOfClass.cs @@ -0,0 +1,77 @@ +using System.Linq; +using System.Runtime.CompilerServices; +using BenchmarkDotNet.Attributes; + +namespace StructLinq.Benchmark +{ + [MemoryDiagnoser, DisassemblyDiagnoser(4)] + public class WhereOnArrayOfClass + { + private Container[] array; + private const int Count = 10000; + + public WhereOnArrayOfClass() + { + 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) + { + if (i.Element % 2 == 0) + sum += i.Element; + } + + return sum; + } + + [Benchmark] + public int LINQ() + { + var sum = 0; + foreach (var i in array.Where(x=> x.Element % 2 == 0)) + { + sum += i.Element; + } + + return sum; + } + + [Benchmark] + public int StructLINQ() + { + var sum = 0; + foreach (var i in array.ToStructEnumerable().Where(x=> x.Element % 2 == 0, x=>x)) + { + sum += i.Element; + } + + return sum; + } + + [Benchmark] + public int StructLINQWithFunction() + { + var sum = 0; + var func = new WhereFunc(); + foreach (var i in array.ToStructEnumerable().Where(ref func, x=>x)) + { + sum += i.Element; + } + + return sum; + } + + private readonly struct WhereFunc : IFunction + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Eval(Container element) + { + return element.Element % 2 == 0; + } + } + } +} From 2d1a8ab07b4bc949ca505ec4c1fba47e98d9d7ae Mon Sep 17 00:00:00 2001 From: Reegeek <10356780+reegeek@users.noreply.github.com> Date: Fri, 18 Dec 2020 09:53:03 +0100 Subject: [PATCH 2/2] [Where] add specific Enumerable for Func --- Documents/BenchmarksResults/WhereOnArray.md | 12 ++--- .../BenchmarksResults/WhereOnArrayOfClass.md | 12 ++--- src/StructLinq.Tests/ReverseTests.cs | 4 +- src/StructLinq.Tests/SkipTests.cs | 6 +-- src/StructLinq.Tests/TakeTests.cs | 6 +-- src/StructLinq.Tests/WhereTests.cs | 6 +-- .../Where/StructEnumerable.Where.cs | 10 ++-- src/StructLinq/Where/WhereEnumerable.cs | 23 ++++++++- src/StructLinq/Where/WhereEnumerator.cs | 48 ++++++++++++++++++- 9 files changed, 96 insertions(+), 31 deletions(-) diff --git a/Documents/BenchmarksResults/WhereOnArray.md b/Documents/BenchmarksResults/WhereOnArray.md index 646eece3..ad2282b1 100644 --- a/Documents/BenchmarksResults/WhereOnArray.md +++ b/Documents/BenchmarksResults/WhereOnArray.md @@ -14,9 +14,9 @@ Intel Core i7-7700 CPU 3.60GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical core ``` -| Method | Mean | Error | StdDev | Ratio | RatioSD | Code Size | Gen 0 | Gen 1 | Gen 2 | Allocated | -|----------------------- |----------:|----------:|----------:|------:|--------:|----------:|------:|------:|------:|----------:| -| Handmaded | 8.121 μs | 0.0320 μs | 0.0300 μs | 1.00 | 0.00 | 58 B | - | - | - | - | -| LINQ | 40.149 μs | 0.1917 μs | 0.1601 μs | 4.94 | 0.02 | 896 B | - | - | - | 48 B | -| StructLINQ | 29.332 μs | 0.2121 μs | 0.1880 μs | 3.61 | 0.03 | 632 B | - | - | - | - | -| StructLINQWithFunction | 16.885 μs | 0.0860 μs | 0.0762 μs | 2.08 | 0.01 | 495 B | - | - | - | - | +| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Code Size | Gen 0 | Gen 1 | Gen 2 | Allocated | +|----------------------- |----------:|----------:|----------:|----------:|------:|--------:|----------:|------:|------:|------:|----------:| +| Handmaded | 8.131 μs | 0.0262 μs | 0.0218 μs | 8.137 μs | 1.00 | 0.00 | 58 B | - | - | - | - | +| LINQ | 46.088 μs | 0.8944 μs | 1.3388 μs | 45.395 μs | 5.82 | 0.15 | 896 B | - | - | - | 48 B | +| StructLINQ | 28.075 μs | 0.0999 μs | 0.0934 μs | 28.084 μs | 3.45 | 0.01 | 632 B | - | - | - | - | +| StructLINQWithFunction | 16.881 μs | 0.1049 μs | 0.0981 μs | 16.905 μs | 2.08 | 0.01 | 495 B | - | - | - | - | diff --git a/Documents/BenchmarksResults/WhereOnArrayOfClass.md b/Documents/BenchmarksResults/WhereOnArrayOfClass.md index 804094dd..f3dcfcb6 100644 --- a/Documents/BenchmarksResults/WhereOnArrayOfClass.md +++ b/Documents/BenchmarksResults/WhereOnArrayOfClass.md @@ -14,9 +14,9 @@ Intel Core i7-7700 CPU 3.60GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical core ``` -| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | Code Size | -|----------------------- |----------:|----------:|----------:|------:|--------:|------:|------:|------:|----------:|----------:| -| Handmaded | 9.610 μs | 0.0691 μs | 0.0540 μs | 1.00 | 0.00 | - | - | - | - | 48 B | -| LINQ | 63.090 μs | 1.2080 μs | 1.0088 μs | 6.58 | 0.11 | - | - | - | 48 B | 48 B | -| StructLINQ | 39.239 μs | 0.6532 μs | 0.6110 μs | 4.08 | 0.07 | - | - | - | - | 48 B | -| StructLINQWithFunction | 19.871 μs | 0.1385 μs | 0.1156 μs | 2.07 | 0.02 | - | - | - | - | 494 B | +| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | Code Size | +|----------------------- |---------:|---------:|---------:|------:|--------:|------:|------:|------:|----------:|----------:| +| Handmaded | 12.79 μs | 0.025 μs | 0.021 μs | 1.00 | 0.00 | - | - | - | - | 62 B | +| LINQ | 66.50 μs | 1.298 μs | 1.903 μs | 5.24 | 0.15 | - | - | - | 48 B | 48 B | +| StructLINQ | 44.21 μs | 0.834 μs | 0.856 μs | 3.47 | 0.07 | - | - | - | - | 48 B | +| StructLINQWithFunction | 19.87 μs | 0.114 μs | 0.106 μs | 1.55 | 0.01 | - | - | - | - | 494 B | diff --git a/src/StructLinq.Tests/ReverseTests.cs b/src/StructLinq.Tests/ReverseTests.cs index 18f07eee..bed1d7c8 100644 --- a/src/StructLinq.Tests/ReverseTests.cs +++ b/src/StructLinq.Tests/ReverseTests.cs @@ -8,12 +8,12 @@ namespace StructLinq.Tests { public class ReverseTests : AbstractEnumerableTests< int, - ReverseEnumerable, RangeEnumerator, StructFunction>, WhereEnumerator>>, + ReverseEnumerable, RangeEnumerator>, WhereEnumerator>, ReverseEnumerator> { protected override - ReverseEnumerable, RangeEnumerator, StructFunction>, WhereEnumerator>> Build(int size) + ReverseEnumerable, RangeEnumerator>, WhereEnumerator> Build(int size) { return StructEnumerable.Range(0, size).Where(x => true).Reverse(x => x); } diff --git a/src/StructLinq.Tests/SkipTests.cs b/src/StructLinq.Tests/SkipTests.cs index fc0acdb3..3490a7fd 100644 --- a/src/StructLinq.Tests/SkipTests.cs +++ b/src/StructLinq.Tests/SkipTests.cs @@ -7,11 +7,11 @@ namespace StructLinq.Tests { public class SkipTests : AbstractEnumerableTests>, WhereEnumerator>>, - SkipEnumerator>>> + SkipEnumerable, WhereEnumerator>, + SkipEnumerator>> { - protected override SkipEnumerable>, WhereEnumerator>> Build(int size) + protected override SkipEnumerable, WhereEnumerator> Build(int size) { var skipEnumerable = StructEnumerable.Range(-1, size + 5).Where(x=>true, x=>x).Skip(5, x=> x); return skipEnumerable; diff --git a/src/StructLinq.Tests/TakeTests.cs b/src/StructLinq.Tests/TakeTests.cs index 8fb2f552..acac011e 100644 --- a/src/StructLinq.Tests/TakeTests.cs +++ b/src/StructLinq.Tests/TakeTests.cs @@ -7,10 +7,10 @@ namespace StructLinq.Tests { public class TakeTests : AbstractEnumerableTests>, WhereEnumerator>>, - TakeEnumerator>>> + TakeEnumerable, WhereEnumerator>, + TakeEnumerator>> { - protected override TakeEnumerable>, WhereEnumerator>> Build(int size) + protected override TakeEnumerable, WhereEnumerator> Build(int size) { return StructEnumerable.Range(-1, size).Where(x=>true, x=>x).Take(size, x=> x); } diff --git a/src/StructLinq.Tests/WhereTests.cs b/src/StructLinq.Tests/WhereTests.cs index f9f3b959..183860de 100644 --- a/src/StructLinq.Tests/WhereTests.cs +++ b/src/StructLinq.Tests/WhereTests.cs @@ -7,10 +7,10 @@ namespace StructLinq.Tests { public class WhereTests : AbstractEnumerableTests>, - WhereEnumerator>> + WhereEnumerable, + WhereEnumerator> { - protected override WhereEnumerable> Build(int size) + protected override WhereEnumerable Build(int size) { var whereEnumerable = StructEnumerable.Range(-1, size).Where(x => x >= -1, x=>x); return whereEnumerable; diff --git a/src/StructLinq/Where/StructEnumerable.Where.cs b/src/StructLinq/Where/StructEnumerable.Where.cs index 07a1dd5d..9fbeaf7b 100644 --- a/src/StructLinq/Where/StructEnumerable.Where.cs +++ b/src/StructLinq/Where/StructEnumerable.Where.cs @@ -18,13 +18,12 @@ public static WhereEnumerable Where> Where(this TEnumerable enumerable, Func predicate, + public static WhereEnumerable Where(this TEnumerable enumerable, Func predicate, Func> _) where TEnumerator : struct, IStructEnumerator where TEnumerable : struct, IStructEnumerable { - var structPredicate = predicate.ToStruct(); - return new WhereEnumerable>(ref structPredicate, ref enumerable); + return new WhereEnumerable(predicate, ref enumerable); } @@ -50,11 +49,10 @@ public static RefWhereEnumerable, TEnumerator, StructFunction> Where(this IStructEnumerable enumerable, Func predicate) + public static WhereEnumerable, TEnumerator> Where(this IStructEnumerable enumerable, Func predicate) where TEnumerator : struct, IStructEnumerator { - var structPredicate = predicate.ToStruct(); - return new WhereEnumerable, TEnumerator, StructFunction>(ref structPredicate, ref enumerable); + return new WhereEnumerable, TEnumerator>(predicate, ref enumerable); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/StructLinq/Where/WhereEnumerable.cs b/src/StructLinq/Where/WhereEnumerable.cs index bcbbe0d0..b8497462 100644 --- a/src/StructLinq/Where/WhereEnumerable.cs +++ b/src/StructLinq/Where/WhereEnumerable.cs @@ -1,4 +1,5 @@ -using System.Runtime.CompilerServices; +using System; +using System.Runtime.CompilerServices; namespace StructLinq.Where { @@ -22,4 +23,24 @@ public WhereEnumerator GetEnumerator() return new WhereEnumerator(ref function, ref enumerator); } } + + public struct WhereEnumerable : IStructEnumerable> + where TEnumerator : struct, IStructEnumerator + where TEnumerable : IStructEnumerable + { + private Func function; + private TEnumerable inner; + public WhereEnumerable(Func function, ref TEnumerable inner) + { + this.function = function; + this.inner = inner; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public WhereEnumerator GetEnumerator() + { + var enumerator = inner.GetEnumerator(); + return new WhereEnumerator(function, ref enumerator); + } + } } diff --git a/src/StructLinq/Where/WhereEnumerator.cs b/src/StructLinq/Where/WhereEnumerator.cs index e541cca5..5533a02c 100644 --- a/src/StructLinq/Where/WhereEnumerator.cs +++ b/src/StructLinq/Where/WhereEnumerator.cs @@ -1,4 +1,5 @@ -using System.Runtime.CompilerServices; +using System; +using System.Runtime.CompilerServices; namespace StructLinq.Where { @@ -46,4 +47,49 @@ public void Dispose() } } + + public struct WhereEnumerator : IStructEnumerator + where TEnumerator : struct, IStructEnumerator + { + #region private fields + private Func predicate; + private TEnumerator enumerator; + #endregion + public WhereEnumerator(Func predicate, ref TEnumerator enumerator) + { + this.predicate = predicate; + this.enumerator = enumerator; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() + { + while (enumerator.MoveNext()) + { + var current = enumerator.Current; + if (!predicate(current)) + continue; + return true; + } + + return false; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Reset() + { + enumerator.Reset(); + } + public readonly TIn Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => enumerator.Current; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() + { + enumerator.Dispose(); + } + + } + } \ No newline at end of file