Проблема с группами и заказами заключается в том, что им требуется знание всей коллекции для выполнения операций. То же самое с агрегатами, такими как min, max, sum, avg и т. Д. Некоторые из этих операций не могут запрашивать истинный тип передаваемого IEnumerable, или это не имеет значения, поскольку они являются «разрушительными» по своей природе, поэтому их приходится создавать рабочая копия. Когда вы объединяете эти вещи вместе, вы получаете как минимум две копии полностью перечислимого; один, созданный предыдущим методом, который повторяется текущим методом, и один, генерируемый текущим методом. Любая копия перечислимого объекта, имеющая ссылку вне запроса (например, перечисляемый источник), также остается в памяти, а перечислимые элементы, которые стали потерянными, остаются до тех пор, пока поток GC не успеет их утилизировать и завершить. Для большого перечисляемого источника все это может создать огромный спрос на кучу.
Кроме того, вложенные агрегаты в предложениях могут очень быстро сделать запрос дорогим. В отличие от СУБД, которая может разработать «план запроса», Linq не настолько умён. Min (), например, требует итерации всего перечисляемого, чтобы найти наименьшее значение указанной проекции. Когда это является критерием предложения Where, хорошая СУБД найдет это значение один раз для контекста, а затем при необходимости внесет его в последующие оценки. Linq просто запускает метод расширения каждый раз, когда он вызывается, и когда у вас есть условие, например enumerable.Where (x => x.Value == enumerable.Min (x2 => x2.Value)), это O (N ^ 2) - сложность операции только для оценки фильтра. Добавьте несколько уровней группировки, и Big-O может легко достичь высокой полиномиальной сложности.
Как правило, вы можете сократить время запроса, выполнив оптимизацию, чтобы СУБД дала тот же запрос. Если значение агрегата может быть известно по всей области запроса (например, result = source.Where(s=>s.Value == source.Min(x=>x.value))
), оцените его как переменную с помощью предложения let
(или внешнего запроса) и замените вызовы Min () псевдонимом. , Повторное перечисление дважды обычно дешевле, чем повторение N ^ 2 раза, особенно если перечисление остается в памяти между итерациями.
Кроме того, убедитесь, что ваш порядок запросов максимально сокращает пространство выборки и как можно дешевле перед началом группировки. Вы можете сделать обоснованные предположения об условиях, которые должны быть оценены дорого, например, где (s => s.Value <порог) .Where (s => s.Value == source.Min (x => x.Value) )) или более кратко, где (s => s.Value x.Value)) (второй работает в C # из-за отложенной оценки состояния, но не для всех языков оценивать лениво). Это сокращает количество оценок Min () до количества элементов, соответствующих первому критерию. Вы можете использовать существующие критерии, чтобы сделать то же самое, если критерии A и B достаточно независимы, чтобы A && B == B && A.