Проблема с производительностью LINQ: суммирование занимает больше времени при меньшем количестве записей

Приложение Silverlight 4 с использованием WCF RIA Services

У меня есть страница с ComboBox и TabControl. Каждый TabItem имеет пользовательский элемент управления, который отображает элемент управления RAD Chart. Поле со списком содержит элементы, которые используются для фильтрации данных, которые пользовательские элементы управления отображают на диаграммах. Когда выбор ComboBox изменяется, страница обновляет набор сущностей, хранящихся в свойстве класса экземпляра/одиночки. Пользовательские элементы управления уведомляются об изменении и получают доступ к обновленной коллекции сущностей, чтобы передать элементу управления диаграммы. Все работает. Когда выбор ComboBox изменяется, пользовательские элементы управления отображают подмножество данных, выбранных на основе выбранного элемента ComboBox.

Что меня смутило, так это время, необходимое для перебора коллекции сущностей при подготовке значений, которые передаются в элемент управления диаграммой. На диаграмме отображаются данные за 12 месяцев, поэтому я итерирую коллекцию 12 раз и использую запрос LINQ, чтобы получить сумму свойств объектов в коллекции. Я выделил проблему производительности для одного запроса LINQ, который выполняет Sum:

Decimal sum = myCollection.Where(m => m.CreationDate.Month == month).Sum(m => (m.SalePrice ?? 0));

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

Вот это кикер...

Верхняя опция ComboBox — «все записи». Итак, в моем тестовом примере при первом использовании приведенной выше строки кода для получения «Суммы» коллекция содержит более 500 записей. Он возвращает сумму за 0,2 секунды. Я изменяю ComboBox на выбор, который фильтрует результаты, поэтому в коллекции будет только 80 записей. Имейте в виду, это не совсем другой набор записей, это просто подмножество из 500, которые соответствуют другим критериям. Приведенная выше строка кода выполняется за 1,2 секунды. Когда я запрашиваю сумму из 500+ записей, это занимает 0,2 секунды. Когда я запрашиваю сумму из 80 записей, это занимает 1,2 секунды.

Что может быть причиной этого?

Спасибо,


person Roger    schedule 10.12.2010    source источник


Ответы (1)


Как вы фильтруете, чтобы создать подмножество? Является ли «Коллекция» самим запросом Linq, выполнение которого отложено до этой строки? Если это так, то разница в скорости, которую вы видите, может быть результатом применения логики фильтрации.

Одна из общих возможностей ускорить это — вычислить все 12 чисел за один проход. Это достаточно просто с помощью оператора GroupBy Linq:

var AllSums = Collection.GroupBy(m=>m.CreationData.Month).ToDictionary(g => g.Key, g => g.Sum(m => m.SalePrice ?? 0));
var JanuarySum = AllSums[January]; 

Это должно превзойти 12 полностью отдельных петель.

Изменить. Вот хорошее описание отложенного выполнения: Linq и отложенное выполнение

person Chris Pitman    schedule 10.12.2010
comment
Я называю это подмножеством, потому что хочу подчеркнуть, что это не совсем другие 80 записей, а всего лишь 80 записей из исходных 500. Я понимаю, что вы имеете в виду под потенциалом отложенного фильтра до выполнения этой строки. На этом мое понимание LINQ заканчивается. Если я выполню Count() для коллекции (как я перед вставленной строкой выше), и она даст 80 записей, будет ли один и тот же фильтр выполняться снова каждый раз, когда я суммирую коллекцию? - person Roger; 10.12.2010
comment
Хм... продолжайте нажимать Enter, прежде чем я закончу вводить свой комментарий... Чтобы создать коллекцию, я создаю новый IEnumerable‹MyObjectType› и устанавливаю его равным другой коллекции. Where( ... и несколько других предложений where. Есть ли в этом новом экземпляре коллекции потенциальная задержка выполнения в предложениях where? При первом запросе коллекции, если она была отложена, будет ли она откладываться каждый раз, когда вы ее запрашиваете? Спасибо... И я попробую ваш Предложение GroupBy после того, как я разберусь с проблемой производительности. - person Roger; 10.12.2010
comment
Если я создам новую коллекцию и заполню ее с помощью запроса LINQ из другой коллекции, возникнут ли какие-либо проблемы с задержкой выполнения в новой коллекции? - person Roger; 11.12.2010
comment
Крис, я использовал ваш пример, чтобы создать переменную, содержащую указанный выше оператор groupby. Теперь полная задержка в 1,2 секунды все еще существует, и это происходит, когда я получаю значение из var: sum = var.Where(o ›= o.Month == month).First().sum; - person Roger; 11.12.2010
comment
добавление к этому последнему комментарию... причина того, что задержка в 1,2 секунды все еще является проблемой, заключается в том, что мне все еще нужно получать значения для каждого из 12 месяцев из: sum = var.Where(o ›= o.Month == месяц).Первый().сумма; - person Roger; 11.12.2010
comment
@Roger Guess: Да, если коллекция является результатом многократного вызова Where, то это всего лишь объект запроса, который не был выполнен. Каждый раз, когда вы выполняете операцию, которая на самом деле возвращает данные (количество, сумма, первый, foreach и т. д.), запрос будет выполняться. Если вам нужно искать все результаты по месяцам, добавьте ToLookup, который производит поиск в конце запроса. ToLookup выполнит запрос только один раз. Я добавлю это к ответу. - person Chris Pitman; 11.12.2010