"Вложенные foreach" против производительности "lambda/linq query" (LINQ-to-Objects)

С точки зрения производительности, что вы должны использовать "Nested foreach" или "lambda/linq queries"?

Ответ 1

Напишите самый четкий код, который вы можете, а затем сравнительный анализ и профиль, чтобы обнаружить любые проблемы с производительностью. Если у вас есть проблемы с производительностью, вы можете поэкспериментировать с другим кодом, чтобы выяснить, быстрее или нет это (все время измеряя как можно более реалистичные данные), а затем вынести решение о том, стоит ли повысить производительность удар.

Прямой подход foreach во многих случаях будет быстрее LINQ. Например, рассмотрим:

var query = from element in list
            where element.X > 2
            where element.Y < 2
            select element.X + element.Y;

foreach (var value in query)
{
    Console.WriteLine(value);
}

Теперь есть два предложения where и предложение select, поэтому каждый конечный элемент должен пройти через три итератора. (Очевидно, что два предложения, которые могут быть объединены в этом случае, но я делаю общий вывод.)

Теперь сравните его с прямым кодом:

foreach (var element in list)
{
    if (element.X > 2 && element.Y < 2)
    {
        Console.WriteLine(element.X + element.Y);
    }
}

Это будет работать быстрее, потому что у него меньше обручей. Скорее всего, вывод на консоль будет затмевать стоимость итератора, и я бы предпочел бы запрос LINQ.

РЕДАКТИРОВАТЬ: Чтобы ответить на петли "вложенные foreach"... обычно те, которые представлены с помощью SelectMany или второго предложения from:

var query = from item in firstSequence
            from nestedItem in item.NestedItems
            select item.BaseCount + nestedItem.NestedCount;

Здесь мы добавляем только один дополнительный итератор, потому что мы уже будем использовать дополнительный итератор для каждого элемента в первой последовательности из-за вложенного цикла foreach. Там все еще немного накладных расходов, в том числе накладные расходы на выполнение проекции в делегате вместо "встроенного" (что-то, о чем я не упоминал ранее), но он все равно не будет сильно отличаться от производительности вложенного foreach.

Это не значит, что вы не можете стрелять в ногу с помощью LINQ, конечно. Вы можете писать безупречно неэффективные запросы, если сначала не занимаетесь своим мозгом, но это далеко не уникально для LINQ...

Ответ 2

Если вы делаете

foreach(Customer c in Customer)
{
  foreach(Order o in Orders)
  {
    //do something with c and o
  }
}

Вы выполните итерации Customer.Count * Order.Count


Если вы делаете

var query =
  from c in Customer
  join o in Orders on c.CustomerID equals o.CustomerID
  select new {c, o}

foreach(var x in query)
{
  //do something with x.c and x.o
}

Вы будете выполнять итерации Customer.Count + Order.Count, потому что Enumerable.Join реализован как HashJoin.

Ответ 3

Это сложнее. В конечном счете, большая часть LINQ-to-Objects является (за кулисами) циклом foreach, но с добавленными накладными расходами на небольшие блоки абстракции/итератора/и т.д. Однако, если вы не делаете очень разные вещи в своих двух версиях (foreach vs LINQ), они должны быть как O (N).

Реальный вопрос: есть ли лучший способ написать ваш специфический алгоритм, что означает, что foreach будет неэффективным? И может ли LINQ сделать это за вас?

Например, LINQ упрощает хеширование/группировку/сортировку данных.

Ответ 4

Это было сказано ранее, но это заслуживает повторения.

Разработчики никогда не знают, где узкое место в производительности, пока не будут выполнены тесты производительности.

То же самое верно для сравнения техники A с техникой B. Если не существует драматической разницы, вам просто нужно ее протестировать. Это может быть очевидно, если у вас есть сценарий O (n) vs O (n ^ x), но поскольку материал LINQ в основном является компилятором, он заслуживает профилирования.

Кроме того, если ваш проект не находится в производстве, и вы профилировали код и обнаружили, что этот цикл замедляет выполнение, оставьте его в зависимости от вашего предпочтения для удобства чтения и обслуживания. Преждевременная оптимизация - дьявол.

Ответ 5

Большое преимущество в том, что использование запросов Linq-To-Objects дает вам возможность легко перевести запрос на PLinq и система автоматически выполнит его работу с правильным количеством потоков для текущей системы.

Если вы используете эту технику на больших наборах данных, это легко станет большой победой для очень маленьких неприятностей.