Linq记忆问题

nan*_*nan 8 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

Jon*_*nna 7

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

确实,每种内存的使用都不同.但是,这里有一个暗示,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.
Run Code Online (Sandbox Code Playgroud)

值得注意的是两件事.一个"合格"的收集并不意味着收集将在那一刻发生,只是它可以在未来的任何时候.二,如果对象仍然在范围内,如果它不再被使用(甚至在某些情况下使用它,但这是另一个细节级别),则可能发生收集.范围规则与程序源有关,并且是程序编写时可能发生的事情(程序员可以添加使用该对象的代码),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.
Run Code Online (Sandbox Code Playgroud)

所以在这种情况下,直到最后才能收集任何相关对象.

但是,请注意,这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;
}
Run Code Online (Sandbox Code Playgroud)

此代码读取文本文件并一次返回每行.如果每一行都有一个动物的名字,我们可以使用它而不是var animals我们的来源filtered.

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

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

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

var filtered = (from animal in animals
                   where animal.StartsWith("ra")
                   select animal).ToList();
Run Code Online (Sandbox Code Playgroud)

使非LINQ等效于LINQ使用

var filtered = FilterAnimals(animals);
Run Code Online (Sandbox Code Playgroud)

您还可以在其中定义:

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

它使用.NET 2.0技术,但即使使用.NET 1.1(尽管有更多代码),您也可以创建从中派生的对象 IEnumerable