繁体   English   中英

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

[英]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乙 北美

有两个基准:

  • Foreach :带有if语句的手动foreach
  • Any :LINQ 的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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM