【问题标题】:Can variables declared inside a for loop affect the performance of the loop?在 for 循环中声明的变量会影响循环的性能吗?
【发布时间】:2017-06-27 15:47:49
【问题描述】:

我已经完成了我的功课,发现反复保证无论您在 for 循环内部还是外部声明变量,它都不会影响性能,并且它实际上编译为相同的 MSIL。但是我一直在摆弄它,发现在循环内移动变量声明确实会带来相当大且一致的性能提升。

我编写了一个小型控制台测试类来测量这种效果。我初始化了一个静态double[] 数组items, 和两个方法对其执行循环操作,将结果写入静态double[] 数组缓冲区。 最初,我的方法是那些我注意到差异的,即复数的大小计算。为长度为 1000000 的 items 数组运行这些 100 次,对于其中变量(6 个 double 变量)在循环内的那个,我始终得到较低的运行时间:例如,32,83 ±0,64 ms v 43,24±0,45 ms,采用 Intel Core 2 Duo @2.66 GHz 的老年配置。我尝试以不同的顺序执行它们,但这并没有影响结果。

然后我意识到计算复数的大小远非最小的工作示例,并测试了两种更简单的方法:

    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 毫秒,Square2() 为 12.07±0.51 毫秒。

我对ILDASM不是很熟悉,但是我把这两种方法都反汇编过了,唯一的区别似乎是局部变量的初始化:

      .locals init ([0] float64 x,
       [1] int32 i,
       [2] bool CS$4$0000)

Square1()v

      .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 上运行它,两种方法的结果几乎相同。我很抱歉发布了这样的误导性观察。

【问题讨论】:

  • 很好的调查,你肯定会获得支持。
  • @NicoRiff:确实,这是一个写得很好的问题。 (可悲的是,虽然我认为答案是微不足道的。)
  • 我等不及@JonSkeet 回答这个问题了
  • 我无法使用给定的代码复制此行为。生成的 IL 肯定会按照声明 locals 的顺序翻转,但我没有看到任何显着的性能差异。
  • 你能展示你用来衡量性能的代码吗?代码第一次运行时是否考虑到了 JIT 编译?

标签: c# for-loop variable-declaration


【解决方案1】:

任何称职的 C# 编译器都会为您执行此类微优化。仅在必要时将变量泄漏到作用域之外。

因此,如果可能,请将double x; 保留在循环内部。

但就个人而言,如果items[i] 是普通旧数据数组访问,那么我会写buffer[i] = items[i] * items[i];。 C 和 C++ 会对此进行优化,但我认为 C# 还没有(还);你的反汇编意味着它没有。

【讨论】:

  • 非常感谢!我曾经坚持在方法开始时声明所有变量的强迫习惯,但从现在开始我会三思而后行。如果我关心性能,我的主要信息是测试这两种安排,因为优化似乎可以双向发挥作用。
  • 模糊的答案是“多年的经验告诉你,范围松散的变量最终会导致代码库完全混乱”。
  • 您的回答似乎是说将变量保持在循环内部和外部之间应该没有性能差异,但这并不能真正解释 OP 所经历的测量差异。
  • 表面上我把这归咎于弹性尺的概念。
  • C# 编译器偶尔会删除局部变量。我不确定它是在什么条件下做到这一点的,但我以前见过它这样做。
【解决方案2】:

介绍一下垃圾收集器对这两种变体的作用会很有趣。

我可以想象,在第一种情况下,变量x 在循环运行时不会被收集,因为它是在外部范围内声明的。

在第二种情况下,x 上的所有句柄将在每次迭代中被删除。

也许您使用新的 C# 4.6 GC.TryStartNoGCRegionGC.EndNoGCRegion 再次运行测试,看看性能影响是否源于 GC。

Prevent .NET Garbage collection for short period of time

【讨论】:

  • 谢谢,这是个好主意。我想已经对其进行测试,但目前我无法访问 .NET 4.6。 SharpDevelop 似乎不支持它。我将尝试升级我的工具并返回问题。
  • 我怀疑这与 GC 有什么关系。 double 是一个值类型,在这种情况下,将被堆栈分配。它不会产生任何需要清理的垃圾。
  • Eric Lippert 在stackoverflow.com/a/14043763/526724 上提供了一些很好的信息
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-16
  • 2011-12-20
  • 2011-05-28
相关资源
最近更新 更多