2010-11-29 5 views
8

Поскольку я довольно новичок в linq, я хотел бы спросить, правильно ли я понял в следующем примере.Linq memory question

Предположим, что у меня очень большая коллекция имен животных (100 тыс. Записей), я хотел бы их фильтровать и обрабатывать отфильтрованные элементы очень трудоемким методом (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(); 
    } 
} 
+0

Что дает вам такое впечатление в первую очередь? – Jay 2010-11-29 15:07:07

+0

@Jay Когда я использовал отладчик, он не входил в запрос до тех пор, пока результат не был использован, что я раньше не знал /. – nan 2010-11-29 15:10:51

+0

Будет ли «животные» собираться в методе non-linq? Май (возможно, наивное) понимание состоит в том, что локальные переменные выходят за рамки (и могут быть собраны) после закрывающей скобки функции, то есть после 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().