2010-11-29 98 views
8

由于我对linq比较陌生,在下面的例子中,我想问一下我的理解是否正确。Linq内存问题

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

这是真的,使用第一种方法原始(大)集合将留在内存后离开方法,并不会被触及GC,而使用linq-less方法集合将被删除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(); 
    } 
} 
+0

首先给你的印象是什么? – Jay 2010-11-29 15:07:07

+0

@Jay当我使用调试器时,它没有进入查询,直到结果被使用,我以前不知道/。 – nan 2010-11-29 15:10:51

+0

``动物`会采用非linq方法吗? 5月(可能天真)的理解是,局部变量超出范围(并且可以收集)在函数的结束括号之后,即在ap之后。Start()在两周后返回(除非它真的异步运行,在这种情况下,这个评论是空的)。 =) – Jens 2010-11-29 15:13:01

回答

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. 

值得一提的两两件事。一个“有资格”收藏并不意味着收藏会在那个时候发生,只是它可以在将来的任何时候收集。二,如果一个对象还没有被再次使用(甚至在某些情况下使用它,但这是另一个细节层次),那么对象仍然在范围内时可能发生集合。范围规则与程序源相关,并且是程序编写时可能发生的事情(程序员可以添加使用该对象的代码),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版本有更多的内存使用(加载每个动物名称在进一步行动之前“ra”进入记忆)。 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技术,但即使使用.NET 1.1(尽管使用更多代码),您也可以在创建衍生自IEnumerable

2

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

如果你想他们一样,你可以叫ToList

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

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

3

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

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