Linq记忆问题

时间:2010-11-29 15:03:42

标签: c# linq memory-management

由于我对linq很新,我想问下面的例子中我的理解是否正确。

我们假设我有非常大的动物名称集(100k记录),我想提交它们并以非常耗时的方法处理过滤的项目(2周)。方法RunWithLinq()RunWithoutLinq()完全相同。

使用第一种方法原始(大)集合在离开方法后将留在内存中是否正确,GC不会触及,而使用无linq方法时,集合将被删除按GC

我很感激你的解释。

class AnimalProcessor
{
    private IEnumerable<string> animalsToProcess;
    internal AnimalProcessor(IEnumerable<string> animalsToProcess)
    {
        this.animalsToProcess = animalsToProcess;
    }
    internal void Start()
    {
        //do sth for 2 weeks with the collection
    }
}
class Program
{
    static void RunWithLinq()
    {
        var animals = new string[] { "cow", "rabbit", "newt", "ram" };
        var filtered = from animal in animals
                       where animal.StartsWith("ra")
                       select animal;
        AnimalProcessor ap = new AnimalProcessor(filtered);
        ap.Start();
    }
    static void RunWithoutLinq()
    {
        var animals = new string[] { "cow", "rabbit", "newt", "ram" };
        var filtered = new List<string>();
        foreach (string animal in animals)
            if(animal.StartsWith("ra")) filtered.Add(animal);
        AnimalProcessor ap = new AnimalProcessor(filtered);
        ap.Start();
    }
}

3 个答案:

答案 0 :(得分:7)

嗯,animals在每种方法结束时都有资格收集,所以严格来说你的陈述是错误的。在非LINQ情况下,animals很快就有资格收集,所以你的陈述的要点是正确的。

确实,每种内存的使用都不同。但是,这里有一个暗示,LINQ在内存使用方面通常更差,而实际上它通常允许比采用其他类型的方法更好的内存使用(尽管有非LINQ方式做同样的事情LINQ方式,当我使用.NET2.0时,我非常喜欢这个特定问题的基本方法。

让我们先考虑两种方法,非LINQ:

var animals = new string[] { "cow", "rabbit", "newt", "ram" };
var filtered = new List<string>();
foreach (string animal in animals)
//at this point we have both animals and filtered in memory, filtered is growing.
    if(animal.StartsWith("ra")) filtered.Add(animal);
//at this point animals is no longer used. While still "in scope" to the source
//code, it will be available to collection in the produced code.
AnimalProcessor ap = new AnimalProcessor(filtered);
//at this point we have filtered and ap in memory.
ap.Start();
//at this point ap and filtered become eligible for collection.

值得注意的是两件事。一个“合格”的收集并不意味着收集将在那一刻发生,只是它可以在未来的任何时候。二,如果对象仍然在范围内,如果它不再被使用(甚至在某些情况下使用它,但这是另一个细节级别),则可能发生收集。范围规则与程序源有关,并且是程序编写时可能发生的事情(程序员可以添加使用该对象的代码),GC集合资格规则与编译程序相关,并且是在程序编写(程序员可以添加这样的代码,但他们没有)。

现在让我们看看LINQ案例:

var animals = new string[] { "cow", "rabbit", "newt", "ram" };
var filtered = from animal in animals
               where animal.StartsWith("ra")
               select animal;
// at this pint we have both animals and filtered in memory.
// filtered defined as a class that acts upon animals.
AnimalProcessor ap = new AnimalProcessor(filtered);
// at this point we have ap, filtered and animals in memory.
ap.Start();
// at this point ap, filtered and animals become eligible for collection.

因此,在这种情况下,直到最后才能收集任何相关对象。

但是,请注意filtered永远不是一个大对象。在第一种情况下,filtered是包含0到n个对象范围内某处的列表,其中n是animals的大小。在第二种情况下,filtered是一个对象,它将根据需要在animals上工作,并且本身具有基本恒定的记忆。

因此,非LINQ版本的峰值内存使用率更高,因为animals仍然存在且filtered包含所有相关对象。随着animals的大小随着程序的变化而增加,实际上非LINQ版本最有可能首先出现严重的内存不足,因为非高峰内存使用状态在非-LINQ案例。

另一件需要考虑的事情是,在现实世界的情况下,我们有足够的项目来担心内存消耗,这就像我们的源不会是一个列表。考虑:

IEnumerable<string> getAnimals(TextReader rdr)
{
  using(rdr)
    for(string line = rdr.ReadLine(); line != null; line = rdr.ReadLine())
      yield return line;
}

此代码读取文本文件并一次返回每行。如果每一行都包含动物的名称,我们可以使用此代替var animals作为filtered的来源。

在这种情况下,虽然LINQ版本的内存使用非常少(一次只需要一个动物名称在内存中),而非LINQ版本的内存使用量要大得多(加载每个动物名称,因为“在进一步行动之前“进入记忆”。 LINQ版本也将在最多几毫秒后开始处理,而非LINQ版本必须首先加载所有内容,然后才能完成一项工作。

因此,LINQ版本可以愉快地处理数十亿字节的数据而不需要使用更多的内存来处理少数数据,而非LINQ版本则会遇到内存问题。

最后,重要的是要注意,这与LINQ本身没有任何关系,因为您使用LINQ的方法与没有LINQ的方法之间存在差异。要使LINQ等效于非LINQ使用:

var filtered = (from animal in animals
                   where animal.StartsWith("ra")
                   select animal).ToList();

使非LINQ等效于LINQ使用

var filtered = FilterAnimals(animals);

您还可以定义:

private static IEnumerable<string> FilterAnimals(IEnumerable<string> animals)
{
  foreach(string animal in animals)
    if(animal.StartsWith("ra"))
      yield return animal;
}

哪个使用.NET 2.0技术,但是在创建从IEnumerable

派生的对象时,即使使用.NET 1.1(尽管有更多代码)也可以这样做

答案 1 :(得分:3)

基于LINQ的方法会将原始集合保留在内存中,但不会使用过滤后的项目存储单独的集合。

要更改此行为,请致电.ToList()

答案 2 :(得分:2)

是的,这是正确的 - 因为filtered变量本质上是查询,而不是查询的结果。迭代它将每次重新评估查询。

如果您想使它们相同,您只需拨打ToList

即可
var filtered = animals.Where(animal => animal.StartsWith("ra"))
                      .ToList();

(我已经将它从查询表达式语法转换为“点符号”,因为在这种情况下它更简单。)