for循环的优化
嗨,大家好我正在写一些目前应该尽可能快地运行的c#代码,通常占用100%的单个核心大约25分钟。 我需要代码保持单核心,因为跨多个核心运行此代码的好处不会像同时多次运行此项目一样好
有问题的代码如下
public Double UpdateStuff(){ ClassA[] CAArray = ClassA[*a very large number indeed*]; Double Value = 0; int length = CAArray.Length; for (int i= 0; i< length ; i++) { Value += CAArray[i].ClassB.Value * CAArray[i].Multiplier; } return Value; }
根据分析器,代码的这个区域负责应用程序的78%的负载,因此似乎是优化的良好候选者。
!!!注意:该函数已从返回类型void更改为返回类型Double,这是伪代码而不是实际代码,以便于阅读
澄清:.net,c#4.0,visual studio 2010,目标机器:windows server 2008 x64
编辑:进一步澄清:此上下文中的所有变量都是公共的而不是属性。 CAArray [i] .ClassB.Value中的值将永远改变不能匹配的双精度数。
你应该删除这个:
int length = CAArray.Length;
并用这个替换循环:
for (int i= 0; i < CAArray.Length; i++) { Value += CAArray[i].ClassB.Value * CAArray[i].Multiplier; }
像原始代码一样存储长度实际上会减慢 C#代码的速度 (反直觉,我知道)。 这是因为如果您在for循环中直接使用Array.Length,则抖动将跳过对循环的每次迭代执行数组边界检查。
此外,我强烈建议并行化这一过程。 最简单的方法是
CAArray.AsParallel().Sum(i => i.ClassB.Value * i.Multiplier);
虽然没有LINQ你可能会获得更快的速度(尽管你需要担心管理多个线程的低级细节)。
尝试:
for (int i = 0; i < length; i++) { var a = CAArray[i]; Value += a.ClassB.Value * a.Multiplier; }
一个区别是在for循环中使用临时变量来保存当前值。
第二个区别,可能更重要的是,将CAArray.Length放入for循环边界而不是count。 编译器优化这样的循环以消除边界检查。
for (int i = 0; i < CAArray.Length; i++) { var curr = CAArray[i]; Value += curr.ClassB.Value * curr.Multiplier; }
您可以做的另一件事是将ClassB,ClassB.Value和Multiplier属性作为字段,如果可以的话。
最后 - 记得检查解决方案属性中的“优化代码”,让编译器优化代码。
另一个微观优化, 可以通过影响非常大的集合的性能,定义一个field
,而不是property
。
for (int i= 0; i< length ; i++) { var a = CAArray[i]; Value += a.ClassB.value_field * a.multiplier_field; }
即使使用属性是MS的建议指南,众所周知,属性引入的开销非常小(但可能与非常大的数据相关)。
希望这可以帮助。
如果你有很多重复的乘数和ClassB.Value
s你可能想要找到所有不同的对,将每对乘以一次然后乘以该对的出现次数。
另外,我会选择AsParallel()
并使用所有核心。
我不知道你对ClassA
有多少控制权,但在我看来,由于Multiplier
和ClassB
是ClassA
属性,你应该修改ClassA
以获得这个计算值的属性。 从理论上讲,您已经将所有这些类实例化,并且已经设置了各自的属性,因此您可以在ClassB.Value
或Multiplier
的设置下轻松计算this.ClassB.Value * this.Multiplier
的所需值。 通过这种方式,您可以降低此循环的成本,而不是将其移至实例化数据。 这是值得的权衡吗? 您需要了解更多有关应用程序中发生的事情的信息,但它会减少此特定function的工作量。 之后你需要做的就是:
public void UpdateStuff(){ ClassA[] CAArray = ClassA[*a very large number indeed*]; Double Value = 0; int length = CAArray.Length; for (int i= 0; i< length ; i++) { Value += CAArray[i].MultipliedClassBValue; } return Value; }
加上这里的优秀人才可以提出的进一步改进。
另一个小改进是使用preincrement作为索引,因为postincrement必须返回迭代器在递增之前的值; 所以,之前的值需要在使用适当的增量进行更改之前复制到某处,因此可以返回。
额外的工作可能有点或很多,但它肯定不能小于零,与preincrement相比,它可以简单地执行递增,然后返回刚改变的值 – 无需复制//保存//等必要的。
- 并行化它。
- 尝试展开循环。 (编译器可能会自行完成此操作。)
还有一点需要注意 – 如果你经常分配非常大的数组(86K +数据),并且每次因为这个对象在LOH上分配而过多地强调GC时,大小就不同了。
由于数组具有大量元素,因此这样的东西比其他循环迭代方法更快。
try { for (int i= 0; ; i++) { var a = CAArray[i]; Value += a.ClassB.value_field * a.multiplier_field; } } catch (IndexOutOfRangeException) { }
虽然不可否认它看起来相当丑陋,绝对不是一种“纯粹的”编程方式。 但同时使用公共字段而不是属性并不纯粹。
除了从删除退出条件中获得的收益之外,CLR 2.0 for X86中的一个奇怪的错误使得for循环运行得更快,如果它被try catch作为Jitter包围,在这种情况下,某种方式更喜欢使用CPU堆栈上的寄存器来存储本地。
首先,它是一个空白,所以它不应该返回任何东西(或者它应该返回一个Double)。 其次,C#一般不使用埃及大括号 – 但这并不重要。
然后你可以尝试使用Linq和lambdas,我认为它可能更快 – 至少更干净!
public void UpdateStuff() { ClassA[] CAArray = new ClassA[large_number]; Double Value = CAArray.Select(x => x.ClassB.Value * x.Multiplier).Sum(); }