LINQ为什么"Enumerable = Enumerable.Skip(N)"慢?

Mar*_*ark 14 c# linq ienumerable

我遇到了LINQ查询性能的问题,所以我创建了一个简单的小例子来演示下面的问题.该代码采用随机的小整数列表,并将分区列表分成几个较小的列表,每个列表总数为10或更少.

问题是(正如我写的那样)代码用N指数增长.这只是一个O(N)问题.当N = 2500时,代码需要10秒才能在我的电脑上运行.

如果有人能解释发生了什么,我会非常高兴.谢谢,马克.

int N = 250;
Random r = new Random();
var work = Enumerable.Range(1,N).Select(x => r.Next(0, 6)).ToList();
var chunks = new List<List<int>>();

// work.Dump("All the work.");  // LINQPad Print
var workEnumerable = work.AsEnumerable();

Stopwatch sw = Stopwatch.StartNew();
while(workEnumerable.Any())  // or .FirstorDefault() != null
{
    int soFar = 0;
    var chunk = workEnumerable.TakeWhile( x => 
                          {
                              soFar += x;               
                              return  (soFar <= 10);
                          }).ToList();
    chunks.Add(chunk);          // Commented out makes no difference.
    workEnumerable = workEnumerable.Skip(chunk.Count); // <== SUSPECT
}
sw.Stop();

// chunks.Dump("Work Chunks.");   // LINQPad Print
sw.Elapsed.Dump("Time elapsed.");
Run Code Online (Sandbox Code Playgroud)

mil*_*ose 10

什么.Skip()是创建一个新的IEnumerable循环源,并且只在第一个N元素之后开始产生结果.你链接谁知道其中有多少相继.每次调用时.Any(),都需要再次遍历所有先前跳过的元素.

一般来说,在LINQ中设置非常复杂的运算符链并重复枚举它是一个坏主意.此外,由于LINQ是一个查询API,Skip()当您尝试实现的内容等于修改数据结构时,类似的方法是一个不好的选择.


seh*_*ehe 5

您可以有效地将Skip()链接到相同的枚举上。在250个列表中,最后一个块将由一个可枚举的惰性枚举创建,该枚举的前面有〜25个“ Skip”枚举器类。

如果这样做的话,您会发现事情变得更快了

workEnumerable = workEnumerable.Skip(chunk.Count).ToList();
Run Code Online (Sandbox Code Playgroud)

但是,我认为整个方法可能会改变。

如何使用标准LINQ来实现相同效果:

http://ideone.com/JIzpml上实时观看

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    private readonly static Random r = new Random();

    public static void Main(string[] args)
    {
        int N = 250;
        var work = Enumerable.Range(1,N).Select(x => r.Next(0, 6)).ToList();

        var chunks = work.Select((o,i) => new { Index=i, Obj=o })
            .GroupBy(e => e.Index / 10)
            .Select(group => group.Select(e => e.Obj).ToList())
            .ToList();

        foreach(var chunk in chunks)
            Console.WriteLine("Chunk: {0}", string.Join(", ", chunk.Select(i => i.ToString()).ToArray()));
    }
}
Run Code Online (Sandbox Code Playgroud)