Проблема с групп и заказов - это то, что они требуют знания всей коллекции для выполнения операций. То же самое с агрегатами, такими как 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 раза, особенно если перечислимое остается в памяти между итерациями.
Кроме того, убедитесь, что ваш запрос упорядочивает пространство образца максимально и как можно дешевле, прежде чем начинать группировку. Вы можете сделать обоснованные предположения об условиях, которые должны оцениваться дорого, например Where (s => s.Value < threshold). Где (s => s.Value == source.Min (x => x.Value))) или более кратко, где (s => s.Value < порог &s.Value == source.Min (x => x.Value)) (второй работает на C# из-за оценки ленивого состояния, но не для всех языки лениво оценивают). Это уменьшает количество оценок Min() до количества элементов, соответствующих первым критериям. Вы можете использовать существующие критерии для того, чтобы делать то же самое, везде, где критерии A и B достаточно независимы, что A & & B == B & & A.
, пожалуйста, разместите свой запрос LINQ для всех, а также приблизительные размеры коллекций, в которых он работает. – Dave
Тройная вложенная «группа» может быть проблемой здесь, хотя я никогда не могу понять «естественный язык» LINQ. –
Вы должны, вероятно, показать нам некоторые аппаратные спецификации также ... –