2014-08-29 50 views
1

試圖加快迭代雖然2個foreach循環在需時約15 seconds`加快通過兩個的foreach迭代循環

foreach (var prodCost in Settings.ProdCostsAndQtys) 
{ 
    foreach (var simplified in Settings.SimplifiedPricing 
     .Where(simplified => prodCost.improd.Equals(simplified.PPPROD) && 
       prodCost.pplist.Equals(simplified.PPLIST))) 
    { 
     prodCost.pricecur = simplified.PPP01; 
     prodCost.priceeur = simplified.PPP01; 
    } 
} 

基本上ProdCostsAndQtys列表是具有5個屬性的對象的列表的時刻,尺寸的列表是798677

SimplifiedPricing列表是一個包含44個屬性的對象列表,這個列表的大小是347,但很可能會變得更大(因此希望現在獲得最佳性能)。

循環遍歷第二個循環中第一個列表中的所有對象,如果兩個條件匹配,它們會用第二個循環替換第一個循環中的兩個屬性。

+1

對此不太確定,但可以嘗試將「Where」函數的結果放入第二個循環外的變量中 – ppetrov 2014-08-29 12:44:37

+1

如果將內部foreah更改爲外部,需要多長時間?如何製作使用ahash函數的自定義EqualityComparer? – 2014-08-29 12:46:11

+0

有多少個獨特的PPPROD和PPLIST值? – hatchet 2014-08-29 12:48:19

回答

4

看來你SimplifiedPricing是一個較小的查找表和較大的名單上的外循環迭代。它看起來好像延遲的主要來源是小列表中每個項目的Equals檢查以匹配較大列表中的每個項目。此外,如果您有匹配項,則更新較大列表中的值,因此多次更新看起來多餘。

考慮到這一點,我會建議在較小的列表中建立一個Dictionary,增加內存消耗,但會大大加快查找時間。首先,我們需要一些東西來保存這本詞典的關鍵。我會假設improdpplist是整數,但它並不適用於這種情況下發揮作用:

public struct MyKey 
{ 
    public readonly int Improd; 
    public readonly int Pplist; 

    public MyKey(int improd, int pplist) 
    { 
     Improd = improd; 
     Pplist = pplist; 
    } 

    public override int GetHashCode() 
    { 
     return Improd.GetHashCode()^Pplist.GetHashCode(); 
    } 

    public override bool Equals(object obj) 
    { 
     if (!(obj is MyKey)) return false; 

     var other = (MyKey)obj; 
     return other.Improd.Equals(this.Improd) && other.Pplist.Equals(this.Pplist); 
    } 
} 

現在,我們有一些一氣呵成比較Pplist和Improd,我們可以使用它作爲一個關鍵包含SimplifiedPricing的字典。

IReadOnlyDictionary<MyKey, SimplifiedPricing> simplifiedPricingLookup = 
    (from sp in Settings.SimplifiedPricing 
    group sp by new MyKey(sp.PPPROD, sp.PPLIST) into g 
    select new {key = g.Key, value = g.Last()}).ToDictionary(o => o.key, o => o.value); 

注意IReadOnlyDictionary。這是展示我們在創建後不能修改這本字典的意圖,使我們能夠安全地並行主循環:

Parallel.ForEach(Settings.ProdCostsAndQtys, c => 
{ 
    SimplifiedPricing value; 
    if (simplifiedPricingLookup.TryGetValue(new MyKey(c.improd, c.pplist), out value)) 
    { 
     c.pricecur = value.PPP01; 
     c.priceeur = value.PPP01; 
    } 
}); 

這應該您的單線程O(n²)循環更改爲並行O(n)循環,有輕微的開銷用於創建simplifiedPricingLookup字典。

+0

+1,非常優雅的答案! – 2014-08-29 13:10:45

+0

確實非常好的答案!但是我需要保持外部列表完好,所以我可以通過應用程序使用它。 – Houlahan 2014-08-29 13:16:33

+0

@Houlahan,當這個操作完成後,這兩個列表都保持完好,它只會修改ProdCostsAndQtys項目的屬性 – Bas 2014-08-29 13:17:34

2

您可以利用多線程與parallel.Foreach:

Parallel.ForEach(Settings.ProdCostsAndQtys, prodCost => 
{ 
    foreach (var simplified in Settings.SimplifiedPricing 
     .Where(simplified => 
     prodCost.improd.Equals(simplified.PPPROD) && 
     prodCost.pplist.Equals(simplified.PPLIST)) 
    { 
     prodCost.pricecur = simplified.PPP01; 
     prodCost.priceeur = simplified.PPP01; 
    } 
} 

但是,如果你有在內存中的列表,這僅適用。有更有效的機制來更新數據庫中的列表。此外,使用linq連接可能會使代碼在可忽略的性能成本下更具可讀性。

4

一個連接應該是更高效:

var toUpdate = from pc in Settings.ProdCostsAndQtys 
       join s in Settings.SimplifiedPricing 
       on new { prod=pc.improd, list=pc.pplist } equals new { prod=s.PPPROD, list=s.PPLIST } 
       select new { prodCost = pc, simplified = s }; 
foreach (var pcs in toUpdate) 
{ 
    pcs.prodCost.pricecur = pcs.simplified.PPP01; 
    pcs.prodCost.priceeur = pcs.simplified.PPP01; 
} 
+0

這將是完美的,但它不會更新Settings.ProdCostsAndQtys與Settings.Simplified定價結果 – Houlahan 2014-08-29 13:17:40

+0

@Houlahan:爲什麼,你在循環中設置了一個斷點? – 2014-08-29 13:25:13