2012-03-14 1 views
2

Я не могу понять, как функция GroupBy() работает быстрее для многопроходного ResultSelector, чем для однопроходной версии.Как многопроцессор GroupBy() может быть быстрее, чем один проход?

Учитывая этот класс:

public class DummyItem 
    { 
     public string Category { get; set; } 
     public decimal V1 { get; set; } 
     public decimal V2 { get; set; } 
    } 

создать массив с 100000 записей с некоторыми случайными данными, а затем итерацию следующий запрос:

ПОДХОД 1: Несколько проходов для категории составляет

var q = randomData.GroupBy(
    x => x.Category, 
    (k, l) => new DummyItem 
    { 
     Category = k, 
     V1 = l.Sum(x => x.V1), // Iterate the items for this category 
     V2 = l.Sum(x => x.V2), // Iterate them again 
    } 
); 

Кажется, что двойное обращение с внутренним перечислимым, где оно суммирует V1 и V2 для каждой категории.

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

ПОДХОД 2: Однопроходный для категории составляет

var q = randomData.GroupBy(
    x => x.Category, 
    (k, l) => l.Aggregate(// Iterate the inner list once per category 
      new decimal[2], 
      (t,d) => 
      { 
       t[0] += d.V1; 
       t[1] += d.V2; 
       return t; 
      }, 
      t => new DummyItem{ Category = k, V1=t[0], V2=t[1] } 
    ) 
); 

Довольно типичных результатов:

'Multiple pass': iterations=5 average=2,961 ms each 
'Single pass': iterations=5 average=5,146 ms each 

Невероятно, но подход 2 занимает в два раз до тех пор, как подход 1. Я бежал многочисленным контрольные показатели, изменяющие количество свойств V *, количество отдельных категорий и другие факторы. В то время как величина разницы в производительности варьируется, подход 2 равен всегда существенно ниже, чем подход 1.

Я пропустил что-то принципиальное здесь? Как подход 1 может быть быстрее, чем подход 2?

(Я чувствую Facepalm приходит ...)


* UPDATE *

После @ ответ Ирка, я думал, что это будет стоить удаление GroupBy() из чтобы убедиться, что простые агрегации в большом списке выполняются так, как ожидалось. Задача состояла в том, чтобы просто вычислить итоговые значения для двух десятичных переменных в том же списке из 100 000 случайных строк.

Результаты продолжали сюрпризы:

SUM: ForEach

decimal t1 = 0M; 
decimal t2 = 0M; 
foreach(var item in randomData) 
{ 
    t1 += item.V1; 
    t2 += item.V2; 
} 

Базовый. Я считаю, что самый быстрый способ получить требуемый результат.

SUM: Multipass

x = randomData.Sum(x => x.V1); 
y = randomData.Sum(x => x.V2); 

SUM: SinglePass

var result = randomData.Aggregate(new DummyItem(), (t, x) => 
{ 
    t.V1 += x.V1; 
    t.V2 += x.V2; 
    return t; 
}); 

Результаты были следующими:

'SUM: ForEach': iterations=10 average=1,793 ms each 
'SUM: Multipass': iterations=10 average=2,030 ms each 
'SUM: Singlepass': iterations=10 average=5,714 ms each 

Неожиданно раскрывает этот вопрос не имеет ничего общегос GroupBy. Поведение в целом согласуется с агрегацией данных. Мое предположение, что лучше всего выполнять агрегацию данных за один проход, просто неверно (возможно, похмелье из моих корней db).

(Facepalm)

Как @Jirka указал на в-подкладку, по-видимому происходя для многопроходной подхода, означает, что он лишь немного медленнее, чем исходно «ForEach». Моя наивная попытка оптимизировать до одного прохода, работала почти в 3 раза медленнее!

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

+0

Спасибо, что поделились своими дополнительными наблюдениями. Не выбрасывайте свою интуицию. Алгоритмы с одним проходом имеют преимущество в производительности для данных более 1 МБ. Но здесь это преимущество было затмевано вызовами методов, происходящими в самом внутреннем (узком) цикле. –

ответ

1

Агрегат должен создать 99,999 записей активации (для вызовов, не входящих в систему). Это компенсирует преимущество одиночного прохода.

Думайте о графе, сумме, среднем и т. Д., Как о оптимизированных особых случаях того, что Агрегат может делать в общем случае.

+1

Спасибо @Jirka. Нет массива, который выделяется только один раз в качестве семени для агрегирования. Для некоторых моих тестов это было всего четыре раза (т. Е. Только четыре категории). При повторном перечислении для каждой категории массив просто обновляется. –

+1

@ degorolls - Вы правы, я сожалею о недосмотре. Я исправил свой ответ. –

+0

Увлекательный! Спасибо @Jirka. У меня было довольно фундаментальное заблуждение, исправленное ... –

 Смежные вопросы

  • Нет связанных вопросов^_^