6

Если говорить о linq to sql, это можно понять. На основе выражения формируется sql запрос и отправляется, когда будет полностью сформирован.

Но если отбросить linq to sql и говорить только о linq to objects, есть ли практическая ценность в ленивости? Почему бы не выполнять все методы сразу?

kernelA
  • 61
  • 3
    Для чего ленивость в SQL? Наверно оптимизация, да? Ну так почему простые запросы должны быть без нее? – EvgeniyZ Dec 14 '20 at 16:19

2 Answers2

11

Ленивость нужна для оптимизации, чтобы не материализировать цепочку в процессе вычисления.

Посмотрите на пример.

Enumerable.Range(1, 10_000_000)
          .Where(n => n % 149 == 7)
          .Where(n => n % 271 == 9)
          .First()

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

В случае ленивого вычисления, у вас не будет в памяти вовсе никаких списков! Цепочка LINQ сначала возьмёт единицу, она не пройдёт через первый фильтр и будет отброшена. Затем возьмёт двойку, она тоже не пройдёт первый фильтр и будет отброшена. и так далее будет продолжаться до числа 7, которое пройдёт первый фильтр, но не пройдёт второй. и тоже будет отброшено. И так далее до тех пор, пока не будет вычислен результат, дальнейшие числа обрабатываться вовсе не будут.


Заметьте, что вы легко можете из ленивого вычисления сделать энергичное:

var l1 = Enumerable.Range(1, 10_000_000).ToList();
var l2 = l1.Where(n => n % 149 == 7).ToList();
var l3 = l2.Where(n => n % 271 == 9).ToList();
var result = l3.First();

Из энергичного же вычисления сделать ленивое куда сложнее.


Возникает закономерный вопрос: хорошо, а если у нас нету в LINQ-запросе ни First, ни Take, ни Skip, и все данные реально нужно обрабатывать — в этом случае выгоды от ленивых вычислений нету?

Оказывается, выгоды есть и в этом случае. Например, энергично вычисленная последовательность занимает много памяти, и её может просто не хватить. (Да она может вообще быть бесконечной, как в ответе @tym32167!)

Выделение большого количества памяти и запись туда данных — гораздо более медленная операция, чем ленивая конвейерная обработка последовательности элемент за элементом, ведь при этом нужно выделять страницы физической памяти, и раз объём данных большой, то они не влезут в быстрый кэш процессора, и придётся работать с намного более медленной памятью.

При ленивой же обработке списка у вас в памяти [обычно] в каждый момент времени лишь данные, относящиеся к одному элементу последовательности, а это [обычно] довольно мало.

(Простое сравнение расхода памяти есть, например, тут.)

VladD
  • 206,799
  • Из энергичного же вычисления сделать ленивое куда сложнее. - а это возможно вообще? – Grundy Dec 15 '20 at 04:29
  • @Grundy: Для вычислений одного значения поможет Lazy<T>, а вот для ленивых списков нужно руками переделывать код на сопрограммы, без этого вроде никак. – VladD Dec 15 '20 at 12:08
  • Ну вот да, про списки я имел ввиду, без переписи не получится ж – Grundy Dec 15 '20 at 12:11
  • @Grundy: Редактирование кода — это и есть «куда сложнее», тут не выйдет локально ограничиться дописыванием явной материализации. – VladD Dec 15 '20 at 12:13
6

В дополнение к предыдущему ответу, с ленивостью можно удобно перебирать бесконечные последовательности, например вроде бы бесконечный цикл

IEnumerable<int> GetFibonacci(){
    int prev = 0; 
    int curr = 1;
yield return prev;
yield return curr;

while(true) 
{
    int next = prev + curr;
    yield return next;
    (prev, curr) = (curr, next);        
}

}

Но при этом у нас нет никаких проблем с перебором первых нескольких записей

foreach(var f in GetFibonacci().Take(10))
    Console.WriteLine(f);

Вывод

0
1
1
2
3
5
8
13
21
34
tym32167
  • 32,857
  • Только хотел дополнить про трюки с бесконечными последовательностями :) – VladD Dec 14 '20 at 16:59
  • @VladD дополни дополни. Лучше 1 полный ответ, чем 2 полуответа :) – tym32167 Dec 14 '20 at 17:00
  • Не-не, я всё равно лучше примера не придумаю. Мне ещё очень нравится вот какой пример: https://stackoverflow.com/a/5396731/276994 (там бесконечная рекурсия) – VladD Dec 14 '20 at 17:01
  • @VladD неплохо неплохо, но такие фокусы могут и голову сломать автору, с этим надо поаккуратней ) – tym32167 Dec 14 '20 at 17:11