加速迭代两个foreach循环

时间:2014-08-29 12:39:59

标签: c# performance linq foreach

尝试加速迭代通过两个foreach循环,此时需要大约15秒

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但很可能会变得更大(因此希望现在获得最佳性能)。

如果两个条件匹配,循环遍历第二个循环中第一个列表中的所有对象,它们将第一个循环中的两个属性替换为第二个循环。

3 个答案:

答案 0 :(得分: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;
}

答案 1 :(得分:4)

您的SimplifiedPricing似乎是一个较小的查找列表,外部循环在较大的列表上迭代。它看起来好像延迟的主要来源是较小列表上每个项目的等于检查以匹配较大列表中的每个项目。此外,如果匹配,则更新较大列表中的值,因此多次更新看起来是多余的。

考虑到这一点,我建议为较小列表中的项目构建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字典的开销很小。

答案 2 :(得分: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 join可能会以可忽略的性能成本使代码更具可读性。