我已经完成了我的作业,并且一再保证,无论是在for循环内部还是外部声明变量,它在性能上都没有区别,它实际上编译为完全相同的MSIL。但是我一直在摆弄它,并发现在循环中移动变量声明确实会导致相当大的一致性能增益。
我已经编写了一个小型控制台测试类来测量这种效果。我初始化了一个静态double[]
数组项,,两个方法对它执行循环操作,将结果写入静态double[]
数组缓冲区。最初,我的方法是那些我注意到差异的方法,即复数的大小计算。为长度为1000000的 items 数组运行这些100次,我得到的变量(6 double
变量)在循环中的运行时间一直较低:例如,32 ,对于采用英特尔酷睿2双核2.66 GHz的老式配置,83±0,64 ms v 43,44±0.45 ms。我尝试以不同的顺序执行它们,但它没有影响结果。
然后我意识到计算复数的大小远非最小的工作示例,并测试了两个更简单的方法:
static void Square1()
{
double x;
for (int i = 0; i < buffer.Length; i++) {
x = items[i];
buffer[i] = x * x;
}
}
static void Square2()
{
for (int i = 0; i < buffer.Length; i++) {
double x;
x = items[i];
buffer[i] = x * x;
}
}
通过这些结果,结果出现了另一种方式:在循环外声明变量似乎更有利:Square1()
的7.07±0.43 ms对Square2()
的12.07±0.51 ms。
我不熟悉ILDASM,但我已经拆解了这两种方法,唯一的区别似乎是局部变量的初始化:
.locals init ([0] float64 x,
[1] int32 i,
[2] bool CS$4$0000)
<{1>} v 中的
Square1()
.locals init ([0] int32 i,
[1] float64 x,
[2] bool CS$4$0000)
中的。根据它,一个Square2()
中的stloc.1
在另一个中是stloc.0
,反之亦然。在更复杂的幅度计算中,MSIL代码甚至代码大小也不同,我在外部声明代码中看到stloc.s i
,其中内部声明代码中有stloc.0
。
那怎么会这样呢?我忽略了什么或者它是真正的效果吗?如果是,它可以在长循环的性能上产生显着的差异,所以我认为值得讨论。
非常感谢您的想法。
编辑:我忽略的一件事是在发布之前在几台计算机上测试它。我现在在i5上运行它,结果几乎完全相同。我对发布这样一个误导性的观察表示歉意。答案 0 :(得分:5)
任何有价值的C#编译器都会为您执行此类微优化。如果必要,只在范围外泄漏变量。
如果可能,请将double x;
置于循环内部。
就个人而言,如果items[i]
是普通的数据阵列访问,那么我会写buffer[i] = items[i] * items[i];
。 C和C ++会对此进行优化,但我不认为C#会(但);你的反汇编暗示它没有。
答案 1 :(得分:1)
分析垃圾收集器对这两种变体的作用会很有趣。
我可以想象,在第一种情况下,循环运行时不会收集变量x
,因为它是在外部范围内声明的。
在第二种情况下,x
上的所有句柄将在每次迭代时删除。
也许您使用新的C#4.6 GC.TryStartNoGCRegion
和GC.EndNoGCRegion
再次运行测试,看看性能影响是否来自GC。