Skip to content

Commit d603aca

Browse files
committed
Improve All(predicate), Any(predicate) from array performance
1 parent 184e518 commit d603aca

File tree

6 files changed

+170
-5
lines changed

6 files changed

+170
-5
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using BenchmarkDotNet.Attributes;
2+
using BenchmarkDotNet.Configs;
3+
using ZLinq;
4+
5+
namespace Benchmark;
6+
7+
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
8+
public class AnyPredicateWithCapturedLambda
9+
{
10+
[Params(10, 100, 1000, 10000)]
11+
public int N;
12+
13+
int[] _nums = default!;
14+
int _target;
15+
16+
[GlobalSetup]
17+
public void Setup()
18+
{
19+
_nums = Enumerable.Range(1, N).ToArray();
20+
_target = N / 2;
21+
}
22+
23+
[Benchmark]
24+
[BenchmarkCategory("Array")]
25+
public bool ArrayExists()
26+
{
27+
return Array.Exists(_nums, i => i > _target);
28+
}
29+
30+
[Benchmark]
31+
[BenchmarkCategory(Categories.ZLinq)]
32+
public bool ZLinq()
33+
{
34+
return _nums.AsValueEnumerable().Any(i => i > _target);
35+
}
36+
37+
[Benchmark]
38+
[BenchmarkCategory(Categories.LINQ)]
39+
public bool Linq()
40+
{
41+
return _nums.Any(i => i > _target);
42+
}
43+
}
44+
45+
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
46+
public class AllPredicateWithCapturedLambda
47+
{
48+
[Params(10, 100, 1000, 10000)]
49+
public int N;
50+
51+
int[] _nums = default!;
52+
int _target;
53+
54+
[GlobalSetup]
55+
public void Setup()
56+
{
57+
_nums = Enumerable.Range(1, N).ToArray();
58+
_target = N;
59+
}
60+
61+
[Benchmark]
62+
[BenchmarkCategory("Array")]
63+
public bool TrueForAll()
64+
{
65+
return Array.TrueForAll(_nums, i => i <= _target);
66+
}
67+
68+
[Benchmark]
69+
[BenchmarkCategory(Categories.ZLinq)]
70+
public bool ZLinq()
71+
{
72+
return _nums.AsValueEnumerable().All(i => i <= _target);
73+
}
74+
75+
[Benchmark]
76+
[BenchmarkCategory(Categories.LINQ)]
77+
public bool Linq()
78+
{
79+
return _nums.All(i => i <= _target);
80+
}
81+
}

sandbox/Benchmark/Program.cs

+7-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,13 @@ internal static class Program
2222
public static int Main(string[] args)
2323
{
2424
#if DEBUG
25-
var bench = new Benchmark.SelectFromEnumerableArray();
26-
bench.ZLinqSelect_FromIReadOnlyListArray();
25+
var bench = new Benchmark.AnyPredicate();
26+
bench.N = 1000;
27+
bench.Setup();
28+
29+
bench.ArrayExists();
30+
bench.Linq();
31+
bench.ZLinq();
2732

2833

2934
return 0;

sandbox/Benchmark/Properties/launchSettings.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"profiles": {
33
"Default": {
44
"commandName": "Project",
5-
"commandLineArgs": "--filter Benchmark.LinqPerfBenchmarks.Count00* -- Default",
5+
"commandLineArgs": "--filter Benchmark.AllPredicateWithCapturedLambda* -- Default",
66

77
// Available config
88
// 1. Default
@@ -64,6 +64,7 @@
6464
// "commandLineArgs": "--filter Benchmark.SimdCount.*",
6565
// "commandLineArgs": "--filter Benchmark.Net80OptimizedBenchmark.*",
6666
// "commandLineArgs": "--filter Benchmark.Net90OptimizedBenchmark.*",
67+
// "commandLineArgs": "--filter Benchmark.AnyPredicate* -- Default",
6768
"workingDirectory": "."
6869
},
6970
"Select Benchmark": {

src/ZLinq/Linq/All.cs

+36
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,41 @@ public static Boolean All<TEnumerator, TSource>(this ValueEnumerable<TEnumerator
3636
return true;
3737
}
3838
}
39+
40+
public static Boolean All<TSource>(this ValueEnumerable<FromArray<TSource>, TSource> source, Func<TSource, Boolean> predicate)
41+
{
42+
ArgumentNullException.ThrowIfNull(predicate);
43+
44+
var array = source.Enumerator.GetSource();
45+
if (array.GetType() != typeof(TSource[]))
46+
{
47+
return All(array, predicate);
48+
}
49+
50+
// The assembly output differs slightly when iterating through array and span.
51+
// According to microbenckmark results, iterating through span was faster for the logic passed to predicate.
52+
var span = (ReadOnlySpan<TSource>)array;
53+
for (int i = 0; i < span.Length; i++)
54+
{
55+
if (!predicate(span[i]))
56+
{
57+
return false;
58+
}
59+
}
60+
return true;
61+
62+
[MethodImpl(MethodImplOptions.NoInlining)]
63+
static Boolean All(TSource[] array, Func<TSource, Boolean> predicate)
64+
{
65+
for (int i = 0; i < array.Length; i++)
66+
{
67+
if (!predicate(array[i]))
68+
{
69+
return false;
70+
}
71+
}
72+
return true;
73+
}
74+
}
3975
}
4076
}

src/ZLinq/Linq/Any.cs

+41-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace ZLinq
1+
using System;
2+
3+
namespace ZLinq
24
{
35
partial class ValueEnumerableExtensions
46
{
@@ -53,5 +55,43 @@ public static Boolean Any<TEnumerator, TSource>(this ValueEnumerable<TEnumerator
5355
return false;
5456
}
5557
}
58+
59+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
60+
public static Boolean Any<TSource>(this ValueEnumerable<FromArray<TSource>, TSource> source, Func<TSource, Boolean> predicate)
61+
{
62+
ArgumentNullException.ThrowIfNull(predicate);
63+
64+
var array = source.Enumerator.GetSource();
65+
if (array.GetType() != typeof(TSource[]))
66+
{
67+
return Any(array, predicate);
68+
}
69+
70+
// The assembly output differs slightly when iterating through array and span.
71+
// According to microbenckmark results, iterating through span was faster for the logic passed to predicate.
72+
var span = (ReadOnlySpan<TSource>)array;
73+
for (int i = 0; i < span.Length; i++)
74+
{
75+
if (predicate(span[i]))
76+
{
77+
return true;
78+
}
79+
}
80+
81+
return false;
82+
83+
[MethodImpl(MethodImplOptions.NoInlining)]
84+
static Boolean Any(TSource[] array, Func<TSource, Boolean> predicate)
85+
{
86+
for (int i = 0; i < array.Length; i++)
87+
{
88+
if (predicate(array[i]))
89+
{
90+
return true;
91+
}
92+
}
93+
return false;
94+
}
95+
}
5696
}
5797
}

src/ZLinq/Linq/AsValueEnumerable.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@ public struct FromArray<T>(T[] source) : IValueEnumerator<T>
368368
int index;
369369

370370
// becareful, don't call AsSpan
371+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
371372
internal T[] GetSource() => source;
372373

373374
public bool TryGetNonEnumeratedCount(out int count)
@@ -379,7 +380,7 @@ public bool TryGetNonEnumeratedCount(out int count)
379380
public bool TryGetSpan(out ReadOnlySpan<T> span)
380381
{
381382
// AsSpan is failed by array variance
382-
if (!typeof(T).IsValueType && source.GetType() != typeof(T[]))
383+
if (source.GetType() != typeof(T[]))
383384
{
384385
span = default;
385386
return false;
@@ -489,6 +490,7 @@ public struct FromList<T>(List<T> source) : IValueEnumerator<T>
489490
{
490491
int index;
491492

493+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
492494
internal List<T> GetSource() => source;
493495

494496
public bool TryGetNonEnumeratedCount(out int count)

0 commit comments

Comments
 (0)