为什么LINQ中的LastOrDefault(predicate)比FirstOrDefault(predicate)快

Azz*_*rel 8 c# linq performance .net-core

介绍

我注意到,在今天使用某些LinQ函数测试性能差异时,它LastOrDefault(predicate)几乎总是比快FirstOrDefault(predicate),这引起了我一些兴趣,因此我编写了一些测试用例,以对两个函数进行相互测试。

我创建了一个integer从1到100万的值列表,如下所示:

List<int> l = new List<int>();
for (int i = 1; i <= 1000000; i++)
  {
    l.Add(i);
  }
Run Code Online (Sandbox Code Playgroud)

然后写了2种方法first()last()

 static void first(List<int> l)
{
  int e = l.FirstOrDefault(x => x == 500000);
  int z = l.FirstOrDefault(x => x == 500000);
  int d = l.FirstOrDefault(x => x == 500000);
  int v = l.FirstOrDefault(x => x == 500000);
  int f = l.FirstOrDefault(x => x == 500000);
}
Run Code Online (Sandbox Code Playgroud)

我在for循环中运行了1000次,在最后一次迭代后设置断点,条件是暂停,但是在我测试的每个单例情况下,LastOrDefault速度都更快。

测试用例

  • 两者都设置为列表中间的元素(LastOrDefault几乎快了一倍)
  • 两者都设置为列表中具有相同距离的元素(例如250k和750k)-再次LastOrDefault更快
  • 我的方法调用的切换顺序(Last()在之前调用First())-没有区别
  • 使用自己的列表运行两个列表,而不是同时在同一列表上运行(同样没有区别)
  • 运行一个,关闭应用程序,重新打开,运行另一个(仍然LastOrDefault更快)
  • 将谓词设置为元素,多数民众赞成不在列表中(仍然相同)
  • 将谓词更改为可乘对象(仍然相同)
  • 创建一个降序列表而不是一个升序列表(没有区别)

    .Net Core版本:3.0

由于调试器可能不准确,因此我再次尝试使用以下代码:

  Console.WriteLine(DateTime.Now + ":" + DateTime.Now.Millisecond);
  for (int i = 0; i < 1000; i++)
  {
    first(l);
  }
  Console.WriteLine(DateTime.Now + ":" + DateTime.Now.Millisecond);
  for (int i = 0; i < 1000; i++)
  {
    last(l);
  }
  Console.WriteLine(DateTime.Now + ":" + DateTime.Now.Millisecond);
Run Code Online (Sandbox Code Playgroud)

还返回了34秒FirstOrDefault和23秒LastOrDefault

在此处输入图片说明

为什么LastOrDefaultFirstOrDefault我所有的测试用例都快得多?

Raw*_*ing 10

steve16351在已删除的注释中指出,.NET Core中Last具有以下优化:

if (source is IList<TSource> list)
{
    for (int i = list.Count - 1; i >= 0; --i)
    {
        TSource result = list[i];
        if (predicate(result))
        {
            found = true;
            return result;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

First没有,并最终运行:

foreach (TSource element in source)
{
    if (predicate(element))
    {
        found = true;
        return element;
    }
}
Run Code Online (Sandbox Code Playgroud)

索引器很可能比使用foreachand迭代器更快地访问列表元素。相比之下,.NET Standard没有针对的优化Last,而是对整个输入进行了迭代,并且Last运行速度比First您预期的要慢。