2012-03-14 58 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:A類單通道總計

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更快?

(我感覺到捂臉來了...)


* UPDATE *

後@爾卡的答案,我認爲這將是值得去除的GroupBy()圖片以查看是否按預期方式執行大型列表上的簡單聚合。該任務僅僅是計算在100,000個隨機行的同一列表上的兩個十進制變量的總計。

結果延續了驚喜:

SUM:的ForEach

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

基線。我相信獲得所需產出的最快方式。

SUM:多道

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。該行爲通常與數據聚合一致。我認爲一次完成數據聚合更好的假設是錯誤的(可能是我的數據庫根源導致的宿醉)。

捂臉

正如@Jirka指出了在襯裏表面上存在的用於多遍方法中,意味着它是僅比基線「的ForEach」慢。我天真的嘗試優化到單通,跑慢了近3倍!

看來,在處理內存中列表時,無論您希望對列表中的項目執行什麼操作,都可能是性能上的一個更大的因素,而不是迭代開銷。

+0

感謝您分享您的其他意見。不要放棄你的直覺。單程算法確實對大約1 MB的數據具有性能優勢。但是,這種優勢在最內層(瓶頸)循環中發生的方法調用顯得相形見絀。 – 2012-03-14 13:24:32

回答

1

集合必須在此過程中創建99,999個激活記錄(用於不可內聯的方法調用)。這抵消了單程的優勢。

將計數,總和,平均值等作爲聚合在一般情況下可以執行的優化特殊情況。

+1

謝謝@Jirka。否該數組僅被分配一次作爲聚合的種子。對於我的一些測試,這隻有四次(即只有四個類別)。迭代每個類別的枚舉時,數組只是更新。 – 2012-03-14 09:33:55

+1

@degorolls - 你是對的,我很抱歉的疏忽。我糾正了我的答案。 – 2012-03-14 10:26:52

+0

迷人!謝謝@Jirka。我有一個相當根本的誤解更正... – 2012-03-14 11:22:48