为什么在搜索列表<T>时,Enumerable.Any(Func<Sucherce,bool>谓词)与带有if语句的foreach相比慢



最近有件事引起了我的好奇心。

Enumerable.Any(Func<TSource, bool> predicate)方法这么慢吗比手动foreach,做同样的事情?

我一直在搞乱一些基准,并想到了这个。我正在检查List<int>包含和项目,大约在列表的一半。

以下是我对几个不同大小的列表的测试结果:

条目:1 000,搜索条目:543

NA
Alloc比
任何4.05NA

编译器可以优化使用foreach中的List<T>(与IEnumerable<T>相比)。我将无法解释细节,但如果你检查生成的IL(例如在sharplab.io),你已经会看到差异-编译器可以调用List<T>.Enumerator上的具体方法,而不是通过callvirt的多态调用(call和Callvirt)。不确定这(以及由于通过接口使用struct List<T>.Enumerator而导致的一次性分配)会导致这样的性能差异。运行时可能会对它进行更多的优化(在sharplab上查看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在评论中正确诊断的那样,区别在于使用list和enumerable。

正如Jon Skeet在评论中建议的那样,我尝试将_items集合从List<int>更改为IEnumerable<int>,以使比较公平。简而言之,这似乎是关键的区别。我的Foreach似乎是利用了这样一个事实,即它知道_items集合是List<T>,而Enumerable.Any方法是IEnumerable<int>

下面是测试结果:

条目:1 000,搜索条目:543

1.001.00
Alloc比

最新更新