加快两个foreach循环的迭代速度
本文关键字:循环 foreach 迭代 速度 两个 | 更新日期: 2023-09-27 18:20:08
尝试加快两个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,但很可能会变得更大(因此现在希望获得最佳性能)。
如果两个条件匹配,则循环在第二个循环中遍历第一个列表中的所有对象——它们将第一个循环中的两个属性替换为第二个环路。
看起来SimplifiedPricing
是一个较小的查找列表,外部循环在一个较大的列表上迭代。在我看来,延迟的主要来源似乎是对较小列表中的每个项目进行Equals检查,以匹配较大列表中的每一个项目。此外,当匹配时,会更新较大列表中的值,因此多次更新看起来是多余的。
考虑到这一点,我建议为较小列表中的项目建立一个Dictionary
,增加内存消耗,但大大加快查找时间。首先我们需要一些东西来固定这本字典的钥匙。我假设improd
和pplist
是整数,但在这种情况下并不重要:
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
字典带来一些开销。
联接应该更高效:
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;
}
您可以使用多个并行线程。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可能会以可忽略的性能代价使代码更具可读性。