C# for循环中声明的变量会影响循环的性能吗?
我已经完成了我的家庭作业,并且反复得到保证,无论在for循环内部还是外部声明变量,都不会对性能造成影响,而且它实际上编译到了非常相同的MSIL。但我一直在摆弄它,并发现在循环中移动变量声明确实会带来可观且一致的性能提升 我编写了一个小的控制台测试类来测量这种效果。我初始化一个静态C# for循环中声明的变量会影响循环的性能吗?,c#,for-loop,variable-declaration,C#,For Loop,Variable Declaration,我已经完成了我的家庭作业,并且反复得到保证,无论在for循环内部还是外部声明变量,都不会对性能造成影响,而且它实际上编译到了非常相同的MSIL。但我一直在摆弄它,并发现在循环中移动变量声明确实会带来可观且一致的性能提升 我编写了一个小的控制台测试类来测量这种效果。我初始化一个静态double[]数组项,两个方法对其执行循环操作,将结果写入一个静态double[]数组缓冲区。最初,我的方法是那些我注意到差异的方法,即复数的量级计算。对长度为1000000的项目数组运行100次,对于变量(6doub
double[]
数组项,两个方法对其执行循环操作,将结果写入一个静态double[]
数组缓冲区。最初,我的方法是那些我注意到差异的方法,即复数的量级计算。对长度为1000000的项目数组运行100次,对于变量(6double
variables)位于循环内部的项目数组,我得到的运行时间始终较低:例如,在使用Intel Core 2 Duo@2.66 GHz的旧配置上,32,83±0,64 ms v 43,24±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()中
在Square2()
中。根据它,一个中的stloc.1
就是另一个中的stloc.0
,反之亦然。在较长的复杂震级计算MSIL代码中,甚至代码大小也不同,我在外部声明代码中看到了stloc.si
,其中内部声明代码中有stloc.0
这怎么可能呢?我是忽略了什么还是真的影响了?如果是的话,它会对长循环的性能产生显著的影响,因此我认为它值得一些讨论
非常感谢你的想法
编辑:我忽略了一件事,那就是在发布之前在几台计算机上测试它。我现在已经在i5上运行了它,两种方法的结果几乎相同。我为发布这样一个误导性的观察结果而道歉 任何称职的C#编译器都会为您执行这样的微优化。仅在必要时将变量泄漏到范围之外
所以保持双x如果可能,代码>循环内部
但就个人而言,如果items[i]
是普通的旧数据数组访问,那么我会写buffer[i]=items[i]*items[i]代码>。C和C++会优化这一点,但我不认为C()是这样的;您的反汇编意味着它没有。分析垃圾收集器对这两个变体所做的工作会很有趣
我可以想象,在第一种情况下,循环运行时不会收集变量x
,因为它是在外部范围内声明的
在第二种情况下,x
上的所有句柄将在每次迭代中删除
也许您可以使用新的C#4.6GC.TryStartNoGCRegion
和GC.EndNoGCRegion
再次运行测试,以查看性能影响是否源于GC
很好的调查,你肯定会赢得一张选票。@尼科里夫:的确,这是一个写得很好的问题。(遗憾的是,虽然我认为答案很琐碎。)我不能等待@JonSkeet的回答,我无法用给定的代码复制这种行为。生成的IL肯定会改变局部变量的声明顺序,但我看不出有任何显著的性能差异。您能展示一下用于度量性能的代码吗?您在第一次运行代码时考虑过JIT编译吗?非常感谢!我过去一直坚持在方法开始时声明所有变量的强迫性习惯,但从现在起我将三思而后行。如果我关心性能的话,我想说的是测试这两种安排,因为优化似乎可以在两个方向上都起作用。模糊的答案是“多年的经验告诉你,范围松散的变量最终会在一个完全混乱的代码库中结束。”.您的答案似乎是说,将变量保留在循环内部和外部之间不应有性能差异,但这并不能真正解释OP所经历的测量差异。有趣的是,我将这完全归咎于弹性标尺的概念。C#编译器偶尔会删除局部变量。我不确定它在什么条件下能做到,但我以前见过它这么做。谢谢,这是个好主意。我想已经测试过了,但目前我无法访问.NET4.6。SharpDevelop似乎并不支持它。我将尝试升级我的工具并返回到问题上来。我怀疑这与GC有任何关系double
是一种值类型,在这种情况下,将被堆栈分配。它不会产生任何垃圾来清理。Eric Lippert在
.locals init ([0] float64 x,
[1] int32 i,
[2] bool CS$4$0000)
.locals init ([0] int32 i,
[1] float64 x,
[2] bool CS$4$0000)