我已经完成了我的作业并且发现重复保证,无论是在for循环内部还是外部声明变量,它在性能上都没有区别,它实际上编译为完全相同的MSIL . 但是我一直在摆弄它,并发现在循环中移动变量声明确实会导致相当大的一致性能增益 .
我已经编写了一个小型控制台测试类来测量这种效果 . 我初始化一个静态 double[]
数组项,两个方法对它执行循环操作,将结果写入静态 double[]
数组缓冲区 . 最初,我的方法是那些我注意到差异的方法,即复数的大小计算 . 对于长度为1000000的项目数组运行这些100次,我得到的变量(6 double
变量)在循环内的运行时间一直较低:例如,32,83±0,64 ms v 43,44使用Intel Core 2 Duo @ 2.66 GHz的老式配置为±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)
在 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上运行它了 results are nearly identical for the two methods. 对于发布这样一个误导性的观察,我道歉 .
2 回答
任何值得盐的C#编译器都会为您执行此类微优化 . 如果必要,仅在范围外泄漏变量 .
因此,如果可能,请将
double x;
保留在循环内部 .就个人而言,如果
items[i]
是普通的数据阵列访问,那么我会写buffer[i] = items[i] * items[i];
. C和C会对此进行优化,但我不会't think C# does (yet); your disassembly implies that it doesn't .分析垃圾收集器对这两种变体的作用将会很有趣 .
我可以想象,在第一种情况下,在循环运行时不收集变量
x
,因为它是在外部范围内声明的 .在第二种情况下,
x
上的所有句柄将在每次迭代时被删除 .也许您再次使用新的C#4.6
GC.TryStartNoGCRegion
和GC.EndNoGCRegion
运行测试,以查看性能影响是否源于GC .Prevent .NET Garbage collection for short period of time