为什么在Select之前运行LINQ OrderBy会花费更多时间?

时间:2019-06-06 08:23:11

标签: c# performance linq ienumerable deferred-execution

在编写编码问题的解决方案时,我发现了LINQ语句的一个有趣行为。我有两种情况:

第一:

arr.Select(x => x + 5).OrderBy(x => x)

第二:

arr.OrderBy(x => x).Select(x => x + 5)

在对System.Diagnostics.Stopwatch进行了一些测试之后,对于长度为100_000的整数数组,我得到了以下结果。

第一种方法:

00:00:00.0000152

第二次:

00:00:00.0073650

现在,我对为什么首先进行订购会花更多时间感到感兴趣。我无法在Google上找到任何东西,所以我自己想到了。

我最终有两个想法:
1.第二种情况必须先转换为IOrderedEnumerable,然后再转换回IEnumerable,而第一种情况只需要转换为IOrderedEnumerable而不是。
2.您最终有2个循环。第一种用于排序,第二种用于选择,而方法1则在1个循环中完成所有操作。

所以我的问题是,为什么在选择之前要花更多的时间进行订购?

2 个答案:

答案 0 :(得分:3)

让我们看一下序列:

private static void UnderTestOrderBySelect(int[] arr) {
  var query = arr.OrderBy(x => x).Select(x => x + 5); 

  foreach (var item in query)
    ;
}

private static void UnderTestSelectOrderBy(int[] arr) {
  var query = arr.Select(x => x + 5).OrderBy(x => x);  

  foreach (var item in query)
    ;
}

// See Marc Gravell's comment; let's compare Linq and inplace Array.Sort
private static void UnderTestInPlaceSort(int[] arr) {
  var tmp = arr;
  var x = new int[tmp.Length];

  for (int i = 0; i < tmp.Length; i++)
    x[i] = tmp[i] + 5;

  Array.Sort(x);
}

为了执行基准测试,我们运行10次并平均6个中间结果:

private static string Benchmark(Action<int[]> methodUnderTest) {
  List<long> results = new List<long>();

  int n = 10;

  for (int i = 0; i < n; ++i) {
    Random random = new Random(1);

    int[] arr = Enumerable
      .Range(0, 10000000)
      .Select(x => random.Next(1000000000))
      .ToArray();

    Stopwatch sw = new Stopwatch();

    sw.Start();

    methodUnderTest(arr);

    sw.Stop();

    results.Add(sw.ElapsedMilliseconds);
  }

  var valid = results
    .OrderBy(x => x)
    .Skip(2)                  // get rid of top 2 runs
    .Take(results.Count - 4)  // get rid of bottom 2 runs
    .ToArray();

  return $"{string.Join(", ", valid)} average : {(long) (valid.Average() + 0.5)}";
}

运行时间并查看结果:

  string report = string.Join(Environment.NewLine,
    $"OrderBy + Select: {Benchmark(UnderTestOrderBySelect)}",
    $"Select + OrderBy: {Benchmark(UnderSelectOrderBy)}",
    $"Inplace Sort:     {Benchmark(UnderTestInPlaceSort)}");

  Console.WriteLine(report);

结果 :( Core i7 3.8GHz,.Net 4.8 IA64)

OrderBy + Select: 4869, 4870, 4872, 4874, 4878, 4895 average : 4876
Select + OrderBy: 4763, 4763, 4793, 4802, 4827, 4849 average : 4800
Inplace Sort:     888, 889, 890, 893, 896, 904 average : 893

我看不出任何明显的差异,Select + OrderBy的效率(约2%的收益)似乎比OrderBy + Select略高。但是,就位排序的性能比Linq的要好得多(5 )。

答案 1 :(得分:2)

根据您所使用的Linq提供程序,可能会对查询进行一些优化。例如。如果您使用某种数据库,则您的提供者很可能会为与以下语句类似的两个语句创建完全相同的查询:

select myColumn from myTable order by myColumn;

无论您是先在Linq订购还是先选择,此性能都应该相同。

由于这似乎没有发生,您可能使用了完全没有优化的Linq2Objects。因此,语句的顺序可能会产生效果,特别是如果您使用Where进行某种过滤,该过滤会过滤掉许多对象,以便以后的语句不会对整个集合起作用。

为了使长话短说:区别很可能来自某些内部初始化逻辑。因为100000个数字的数据集并不大-至少还不够大-甚至一些快速初始化也会产生很大的影响。