在大数据集的可枚举LINQ查询结果上使用ToList() - 效率问题?

时间:2012-07-24 18:12:59

标签: c# linq optimization .net-4.0 type-conversion

我一直在我正在编写的应用程序中大量使用LINQ查询,而我一直遇到的一种情况是必须将LINQ查询结果转换为列表以便进一步处理(我有我想要列表的原因。)

我希望能够更好地了解此列表转换中发生的情况,以防因为我现在多次使用它而导致效率低下。所以,给定我执行这样的折线:

var matches = (from x in list1 join y in list2 on x equals y select x).ToList();

问题:

  1. 除了创建新列表及其人口之外,是否有任何开销,并且引用了查询返回的Enumerable中的元素?

  2. 您认为效率低吗?

  3. 有没有办法让LINQ查询直接生成一个列表,以避免在这种情况下需要转换?

5 个答案:

答案 0 :(得分:5)

好吧,它创建了一个数据副本。 可能效率低下 - 但这取决于发生了什么。如果您需要最后List<T>List<T>通常会尽可能接近您的效率。唯一的例外是,如果你要只是进行转换并且源已经是一个列表 - 那么使用ConvertAll会更有效率,因为它可以创建支持数组大小合适的。

如果需要流式传输数据 - 例如你只是要对它进行foreach,并采取不影响原始数据源的行动 - 然后调用ToList肯定是效率低下的潜在根源。它将强制评估整个list1 - 如果这是一个懒惰评估的序列(例如“来自随机数发生器的前1,000,000个值”)那么那就不好了。请注意,当您正在进行联接时,只要您尝试从序列中提取第一个值,就会对{{1>} 进行评估(无论是否为了填充列表) )。

您可能希望在后台阅读我的Edulinq post on ToList以了解正在发生的事情 - 至少在一种可能的实施方式中。

答案 1 :(得分:1)

  1. 除了那些已经提到你的人之外,没有任何其他的大概。

  2. 我会说是,但这取决于具体的应用场景。顺便说一下,一般最好避免额外的调用。 (我认为这很明显)。

  3. 我不敢。 LINQ query返回数据序列可能是无限序列。转换为List<T>,您可以将其设置为finit,也可以进行索引访问,这在 sequence stream 中是不可能的。

  4. 建议:避免需要List<T>的情况。顺便提一下,如果你需要它,可以在当前时刻以更少的数据向内推。

    希望这有帮助。

答案 2 :(得分:1)

除了已经说过的内容之外,如果您加入的最初两个列表已经非常大,那么创建第三个(创建两者的“交集”)可能会导致内存不足错误。如果您只是迭代LINQ语句的结果,那么您将大大减少内存使用量。

答案 3 :(得分:0)

Enumerable.ToList(source)基本上只是对new List(source)的调用。

此构造函数将测试source是否为ICollection<T>,以及是否分配了适当大小的数组。在其他情况下,即源是LINQ查询的大多数情况下,它将分配具有默认初始容量(四个项目)的阵列,并根据需要将容量增加一倍来增加它。每次容量加倍时,都会分配一个新数组,并将旧数组复制到新数组中。

如果您的列表中包含大量项目(我们可能至少会谈论数千个),这可能会带来一些开销。一旦列表增长超过85 KB,开销就会很大,因为它会在大对象堆上分配,而大对象堆不会被压缩并且可能会受到内存碎片的影响。请注意,我正在引用列表中的数组。如果T是引用类型,则该数组仅包含引用,而不包含实际对象。那些对象则不计入85 KB的限制。

如果你可以准确地估计你的序列的大小(你可能会高估一点而不是低估一点),你可以删除一些开销。例如,如果您只对实现.Select()的内容运行ICollection<T>运算符,则您知道输出列表的大小。

在这种情况下,这种扩展方法会减少这种开销:

public static List<T> ToList<T>(this IEnumerable<T> source, int initialCapacity)
{
    // parameter validation ommited for brevity

    var result = new List<T>(initialCapacity);

    foreach (T item in source)
    {
        result.Add(item);
    }

    return result;
}

在某些情况下,您创建的列表只是要替换已经存在的列表,例如从以前的运行。在这些情况下,如果重用旧列表,则可以避免相当多的内存分配。只有当你没有对那个旧列表的并发访问时,这才有效,如果新列表通常比旧列表小得多,我就不会这样做。如果是这种情况,您可以使用此扩展方法:

public static void CopyToList<T>(this IEnumerable<T> source, List<T> destination)
{
    // parameter validation ommited for brevity

    destination.Clear();

    foreach (T item in source)
    {
        destination.Add(item);
    }
}

话虽如此,我认为.ToList()效率低下吗?不,如果你有记忆,并且你将重复使用该列表,要么随机索引到它,要么多次迭代它。

现在回到你的具体例子:

var matches = (from x in list1 join y in list2 on x equals y select x).ToList(); 

以某种其他方式可能更有效率,例如:

var matches = list1.Intersect(list2).ToList();

如果list1和list2不包含重复项,则会产生相同的结果,如果list2很小,则效率非常高。

通常,唯一能够真正了解的方法是使用典型工作负载来衡量。

答案 4 :(得分:0)

  1. 大多数开销发生在列表创建之前,如与db的连接,将数据转换为
    一个适配器,对于.NET类型,.NET需要决定它的数据类型/结构...

  2. 效率是非常相对的术语。对于SQL不强的程序员来说效率很高, 更快地开发(相对于旧的ADO)1中详述的开销。

  3. 另一方面,LINQ可以从db本身调用程序,这已经更快了。 我建议你进行下一次测试:

    • 以最大数据量运行您的程序并测量时间。
    • 使用一些db过程将数据导出到文件(如XML,CSV,....)并尝试构建列表 从该文件中测量时间。 然后你可以看出差异是否显着。 但是第二种方式对于程序员而言效率较低,但可以缩短运行时间。