Вопрос памяти Linq - PullRequest
       1

Вопрос памяти Linq

8 голосов
/ 29 ноября 2010

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

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

Ответы [ 3 ]

7 голосов
/ 29 ноября 2010

Ну, 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, скорее всего, столкнется с серьезным дефицитом памяти в первую очередь из-за того, что пиковое использование памяти хуже в non-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, но вы можете сделать то же самое даже с .NET 1.1 (хотя и с большим количеством кода) при создании объекта, полученного из IEnumerable

3 голосов
/ 29 ноября 2010

Метод на основе LINQ сохраняет исходную коллекцию в памяти, но не сохраняет отдельную коллекцию с отфильтрованными элементами.

Чтобы изменить это поведение, позвоните .ToList().

2 голосов
/ 29 ноября 2010

Да, все верно - потому что переменная filtered, по сути, является запросом , а не результатами запроса.Итерирование повторяет запрос каждый раз.

Если вы хотите сделать их одинаковыми, вы можете просто позвонить ToList:

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

(я преобразовалот синтаксиса выражения запроса к «точечной нотации», потому что в этом случае это проще.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...