Linq to objects 比普通 C# 慢 20 倍。有没有办法加快速度?

Posted

技术标签:

【中文标题】Linq to objects 比普通 C# 慢 20 倍。有没有办法加快速度?【英文标题】:Linq to objects is 20 times slower than plain C#. Is there a way to speed it up? 【发布时间】:2020-04-01 09:44:36 【问题描述】:

如果我只需要数组的最大值 [或 3 个最大的项],我使用 myArray.OrderBy(...).First() [或 myArray.OrderBy(...).Take( 3)],它比调用 myArray.Max() 慢 20 倍。有没有办法编写更快的 linq 查询?这是我的示例:

using System;
using System.Linq;

namespace ConsoleApp1

    class Program
    
        static void Main(string[] args)
        
            var array = new int[1000000];
            for (int i = 0; i < array.Length; i++)
            
                array[i] = i;
            

            var maxResults = new int[10];
            var linqResults = new int[10];
            var start = DateTime.Now;

            for (int i = 0; i < maxResults.Length; i++)
            
                maxResults[i] = array.Max();
            
            var maxEnd = DateTime.Now;

            for (int i = 0; i < maxResults.Length; i++)
            
                linqResults[i] = array.OrderByDescending(it => it).First();
            
            var linqEnd = DateTime.Now;

            // 00:00:00.0748281
            // 00:00:01.5321276
            Console.WriteLine(maxEnd - start);
            Console.WriteLine(linqEnd - maxEnd);
            Console.ReadKey();
        
    

【问题讨论】:

var linqResults = array.OrderByDescending(it =&gt; it).Take(10).ToArray(); 获得最大值是 O(n),而排序可能是 O(n log(n))。因此,不要排序以获得最大值。 为什么当它是array.Max()时你认为你没有使用Linq。您在两者中都使用了 Linq,只是以更长的方式执行其中一个。 请阅读 Speed Rant:ericlippert.com/2012/12/17/performance-rant Linq-to-Sql 我想优化为不对整个表进行排序,然后只从中取出一项。这就是为什么我认为 Linq-to-objects 足够聪明,不会对整个数组进行排序。 【参考方案1】:

您在循环中对初始数组10 次进行排序:

    for (int i = 0; i < maxResults.Length; i++)
    
        linqResults[i] = array.OrderByDescending(it => it).First();
    

让我们这样做一次

    // 10 top item of the array
    var linqResults = array
      .OrderByDescending(it => it)
      .Take(10)
      .ToArray(); 

请注意

    for (int i = 0; i < maxResults.Length; i++)
    
         maxResults[i] = array.Max();
    

只需重复 相同 Max10 次(它不会返回 10 ***项目)

【讨论】:

不过,获得最大值 10 可以在 O(n) 中完成。猜猜挑战是 linq 要求。 我已经做了 10 次,只是为了更准确地测量时间。我只想做一次。 从技术上讲,我们可以在 O(N) 中完成,例如我们可以使用Min Heap:我们循环array,同时将项目添加到堆中;当它的大小达到11 时,我们从堆中取出(并丢弃)顶部(最小)项。 .Net 中也没有内置 MinHeap(或 PriorityQueue)。【参考方案2】:

最大方法耗时为O(n),最佳时间排序为O(n log(n)) 您的代码的第一个错误是您订购了 10 次,这是最糟糕的情况。您可以像 Dmitry 回答的那样订购一次并拿走 10 个。 而且,调用 Max 方法 10 次不会给你 10 个最大值,只是 10 次的最大值。

但是 Max 方法只迭代一次列表并将 Max 值保存在单独的变量中。您可以重写此方法来迭代您的数组并在您的 maxResults 中保留 10 个最大值,这是您获得结果的最快方式。

【讨论】:

令人印象深刻的是除了主题之外还有多少人在谈论。我叫它10次只是为了更准确地测量时间。我一直在寻找的东西,但在 linq 中,在我的答案中 (morelinq)。 所以,您的答案是 Max 和 OrderBy 方法的时间消耗。最大算法时间为 O(n),排序时间为 O(n log n)。没有办法以 Max 方法更快的方式使用 OrderBy 或 OrderByDescending。【参考方案3】:

似乎其他人已经填补了微软在 linq-to-objects 中留下的效率差距: https://morelinq.github.io/3.1/ref/api/html/M_MoreLinq_MoreEnumerable_PartialSort__1_3.htm

【讨论】:

以上是关于Linq to objects 比普通 C# 慢 20 倍。有没有办法加快速度?的主要内容,如果未能解决你的问题,请参考以下文章

从LINQ开始之LINQ to Objects(上)

从LINQ开始之LINQ to Objects(上)

LINQ to objects

Linq之旅:Linq入门详解(Linq to Objects)

Linq之旅:Linq入门详解(Linq to Objects)

C# Linq to XML 读取多个带有属性的标签