![](/img/trans.png)
[英]How do I create an expression tree for Enumerable.Any<T>(IEnumerable<T>, Func<T,bool>)?
[英]Why is the Enumerable.Any(Func<TSource, bool> predicate) slow compared to a foreach with an if statement when searching a List<T>
最近有件事激起了我的好奇心。。
为什么Enumerable.Any(Func<TSource, bool> predicate)
方法比手动 foreach慢很多,当它们做同样的事情时?
我一直在搞乱一些基准并想到了这一点。 我正在检查一个List<int>
包含的项目,该项目大约位于列表的一半。
以下是我对列表的几种不同大小的测试结果:
项目:1 000,搜索项目:543
方法 | 意思 | 比率 | 已分配 | 分配比例 |
---|---|---|---|---|
Foreach | 838.3 纳秒 | 1.00 | - | 北美 |
任何 | 3,348.8 纳秒 | 4.05 | 40乙 | 北美 |
项目:10 000,搜索项目:5 432
方法 | 意思 | 比率 | 已分配 | 分配比例 |
---|---|---|---|---|
Foreach | 7.988 我们 | 1.00 | - | 北美 |
任何 | 30.991 我们 | 3.88 | 40乙 | 北美 |
项目:100 000,搜索项目:54 321
方法 | 意思 | 比率 | 已分配 | 分配比例 |
---|---|---|---|---|
Foreach | 82.35 我们 | 1.00 | - | 北美 |
任何 | 328.86 我们 | 4.00 | 40乙 | 北美 |
有两个基准:
if
语句的手动foreach
Any
方法(变成Enumerable.Any
)这是我的基准测试代码(使用 BenchmarkDotNet,以发布模式运行的 .NET 6.0 控制台应用程序):
[MemoryDiagnoser(displayGenColumns: false)]
[HideColumns("Error", "StdDev", "RatioSD")]
public class Benchmarks
{
private readonly List<int> _items;
private readonly Func<int, bool> _filter;
public Benchmarks()
{
_items = Enumerable.Range(1, 10_000).ToList();
_filter = x => x == 5432;
}
[Benchmark(Baseline = true)]
public bool Foreach()
{
if (_items is null)
{
throw new ArgumentNullException(nameof(_items));
}
if (_filter is null)
{
throw new ArgumentNullException(nameof(_filter));
}
foreach (var item in _items)
{
if (_filter(item))
{
return true;
}
}
return false;
}
[Benchmark]
public bool Any()
{
return _items.Any(_filter);
}
}
Any方法慢了 4 倍并且分配了一些内存,尽管我尽了最大的努力来优化它。
我试图通过将谓词 ( Func<int, bool>
) 缓存在变量 ( _filter
) 中来加快Any方法的速度。 但是,它仍然分配 40B,我不知道为什么......
反编译时, Any方法变成Enumerable.Any(Func<TSource, bool> predicate)
方法:
public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
if (source == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
}
if (predicate == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.predicate);
}
foreach (TSource element in source)
{
if (predicate(element))
{
return true;
}
}
return false;
}
Any方法与Foreach方法有何不同? 只是好奇...
编译器可以优化在 foreach 中使用List<T>
(与IEnumerable<T>
相比)。 我无法详细解释,但如果您检查生成的 IL(例如在sharplab.io ),您已经会看到差异 - 编译器可以调用List<T>.Enumerator
上的具体方法,而不是通过callvirt
进行多态调用(呼叫和 Callvirt )。 不确定这(以及由于使用struct List<T>.Enumerator
通过接口进行的一次分配)会导致这种性能差异。 可能运行时可以进一步优化它(如果您想尝试更深入,请查看sharplab.io上的 JIT Asm 差异)。
如果您检查Enumerable.Any
的源代码,您会发现它使用相同的foreach
循环,区别归结为使用IEnumerable
接口:
public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
if (source == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
}
if (predicate == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.predicate);
}
foreach (TSource element in source)
{
if (predicate(element))
{
return true;
}
}
return false;
}
因此,正如@Jon Skeet在评论中正确诊断的那样,区别在于使用列表与可枚举。
正如 Jon Skeet 在评论中建议的那样,我尝试将_items
集合从List<int>
更改为IEnumerable<int>
以使比较公平。 简而言之,这似乎是关键的区别。 我的Foreach似乎正在利用它知道_items
集合是List<T>
而Enumerable.Any
方法采用IEnumerable<int>
的事实。
以下是基准测试结果:
项目:1 000,搜索项目:543
方法 | 意思 | 比率 | 已分配 | 分配比例 |
---|---|---|---|---|
Foreach | 2.126 我们 | 1.00 | 40乙 | 1.00 |
任何 | 2.131 我们 | 1.00 | 40乙 | 1.00 |
项目:10 000,搜索项目:5 432
方法 | 意思 | 比率 | 已分配 | 分配比例 |
---|---|---|---|---|
Foreach | 21.35 我们 | 1.00 | 40乙 | 1.00 |
任何 | 21.20 我们 | 0.99 | 40乙 | 1.00 |
项目:100 000,搜索项目:54 321
方法 | 意思 | 比率 | 已分配 | 分配比例 |
---|---|---|---|---|
Foreach | 220.7 我们 | 1.00 | 40乙 | 1.00 |
任何 | 219.1 我们 | 0.99 | 40乙 | 1.00 |
使用IEnumerable<int>
时,这两种方法执行相同的操作。 谢谢乔恩双向飞碟!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.