在循环之前或循环中声明variables之间的区别?

我一直想知道,一般来说,在循环之前是否声明一个抛出variables,而不是在循环内部重复,会产生任何(性能)​​差异? Java中的一个(相当无意义的)例子:

a)循环之前的声明:

double intermediateResult; for(int i=0; i < 1000; i++){ intermediateResult = i; System.out.println(intermediateResult); } 

b)循环内声明(重复):

 for(int i=0; i < 1000; i++){ double intermediateResult = i; System.out.println(intermediateResult); } 

哪一个更好, ab

我怀疑重复的variables声明(例子b在理论上造成了更多的开销,但是编译器足够聪明以至于无关紧要。 示例b具有更紧凑的优点,并将variables的范围限制在使用位置。 不过,我倾向于按照例子编码。

编辑:我特别感兴趣的Java案例。

哪个更好, ab

从性能angular度来看,你必须测量它。 (在我看来,如果你能衡量一个区别,编译器不是很好)。

从维护的angular度来看, b更好。 在尽可能最窄的范围内声明和初始化variables。 不要在声明和初始化之间留下空隙,也不要污染你不需要的命名空间。

那么我运行你的A和B例子,每次循环1亿次(JVM – 1.5.0)

答:平均执行时间:.074秒

B:平均执行时间:0.067秒

令我惊讶的是B稍微快了点。 如果你能准确地衡量这一点,就像现在的计算机一样,它很难说。 我会用A的方式编码,但我会说这并不重要。

这取决于语言和确切的用途。 例如,在C#1中没有任何区别。 在C#2中,如果局部variables被匿名方法(或C#3中的lambdaexpression式)捕获,它可能会产生非常重要的差异。

例:

 using System; using System.Collections.Generic; class Test { static void Main() { List<Action> actions = new List<Action>(); int outer; for (int i=0; i < 10; i++) { outer = i; int inner = i; actions.Add(() => Console.WriteLine("Inner={0}, Outer={1}", inner, outer)); } foreach (Action action in actions) { action(); } } } 

输出:

 Inner=0, Outer=9 Inner=1, Outer=9 Inner=2, Outer=9 Inner=3, Outer=9 Inner=4, Outer=9 Inner=5, Outer=9 Inner=6, Outer=9 Inner=7, Outer=9 Inner=8, Outer=9 Inner=9, Outer=9 

所不同的是,所有的动作都捕捉到相同的outervariables,但是每个动作都有自己独立的innervariables。

以下是我在.NET中编写和编译的内容。

 double r0; for (int i = 0; i < 1000; i++) { r0 = i*i; Console.WriteLine(r0); } for (int j = 0; j < 1000; j++) { double r1 = j*j; Console.WriteLine(r1); } 

当CIL被重新编译回代码时,这就是我从.NET Reflector获得的内容。

 for (int i = 0; i < 0x3e8; i++) { double r0 = i * i; Console.WriteLine(r0); } for (int j = 0; j < 0x3e8; j++) { double r1 = j * j; Console.WriteLine(r1); } 

所以编译后两者看起来完全一样。 在托pipe语言中,代码被转换成CL /字节代码,并在执行时被转换成机器语言。 所以在机器语言中,可能不会在堆栈上创build一个double。 它可能只是一个寄存器,因为代码反映它是WriteLine函数的临时variables。 对于循环,有一整套优化规则。 所以普通人不应该担心,特别是在pipe理语言。 有些情况下,您可以优化pipe理代码,例如,如果您必须使用string a; a+=anotherstring[i]连接大量的stringstring a; a+=anotherstring[i] string a; a+=anotherstring[i] vs使用StringBuilder 。 两者之间的performance有很大的差异。 编译器无法优化代码的情况很多,因为它无法弄清楚更大范围内的意图。 但它可以为你优化基本的东西。

这是VB.NET中的一个小问题。 Visual Basic结果不会在此示例中重新初始化variables:

 For i as Integer = 1 to 100 Dim j as Integer Console.WriteLine(j) j = i Next ' Output: 0 1 2 3 4... 

这将首次打印0(Visual Basicvariables在声明时具有默认值!),但每次都是在这之后。

如果你添加一个= 0 ,但是,你会得到你所期望的:

 For i as Integer = 1 to 100 Dim j as Integer = 0 Console.WriteLine(j) j = i Next 'Output: 0 0 0 0 0... 

这是语言依赖 – IIRC C#优化这个,所以没有任何区别,但是JavaScript(例如)每次都会执行整个内存分配。

我会一直使用A(而不是依靠编译器),也可能会重写为:

 for(int i=0, double intermediateResult=0; i<1000; i++){ intermediateResult = i; System.out.println(intermediateResult); } 

这仍然将intermediateResult限制在循环的作用域中,但是在每次迭代过程中不会重新声明。

我做了一个简单的testing:

 int b; for (int i = 0; i < 10; i++) { b = i; } 

VS

 for (int i = 0; i < 10; i++) { int b = i; } 

我用gcc-5.2.0编译了这些代码。 然后我拆开这两个代码的main(),结果就是:

1º:

  0x00000000004004b6 <+0>: push rbp 0x00000000004004b7 <+1>: mov rbp,rsp 0x00000000004004ba <+4>: mov DWORD PTR [rbp-0x4],0x0 0x00000000004004c1 <+11>: jmp 0x4004cd <main+23> 0x00000000004004c3 <+13>: mov eax,DWORD PTR [rbp-0x4] 0x00000000004004c6 <+16>: mov DWORD PTR [rbp-0x8],eax 0x00000000004004c9 <+19>: add DWORD PTR [rbp-0x4],0x1 0x00000000004004cd <+23>: cmp DWORD PTR [rbp-0x4],0x9 0x00000000004004d1 <+27>: jle 0x4004c3 <main+13> 0x00000000004004d3 <+29>: mov eax,0x0 0x00000000004004d8 <+34>: pop rbp 0x00000000004004d9 <+35>: ret 

VS

  0x00000000004004b6 <+0>: push rbp 0x00000000004004b7 <+1>: mov rbp,rsp 0x00000000004004ba <+4>: mov DWORD PTR [rbp-0x4],0x0 0x00000000004004c1 <+11>: jmp 0x4004cd <main+23> 0x00000000004004c3 <+13>: mov eax,DWORD PTR [rbp-0x4] 0x00000000004004c6 <+16>: mov DWORD PTR [rbp-0x8],eax 0x00000000004004c9 <+19>: add DWORD PTR [rbp-0x4],0x1 0x00000000004004cd <+23>: cmp DWORD PTR [rbp-0x4],0x9 0x00000000004004d1 <+27>: jle 0x4004c3 <main+13> 0x00000000004004d3 <+29>: mov eax,0x0 0x00000000004004d8 <+34>: pop rbp 0x00000000004004d9 <+35>: ret 

这是不一样的结果。 这不是两个守则产生同样的东西的certificate吗?

在我看来,b是更好的结构。 在a中,intermediateResult的最后一个值在你的循环完成之后继续存在。

编辑:这与值types没有太大的区别,但引用types可能有点重要。 就我个人而言,我喜欢尽快清理variables,并且b为你做这些,

我怀疑一些编译器可以优化两个是相同的代码,但肯定不是全部。 所以我会说你和前者有更好的关系。 后者的唯一原因是,如果你想确保声明的variables在你的循环中使用。

作为一般规则,我将我的variables声明在最可能的范围内。 所以,如果你没有在循环外使用intermediateResult,那么我会和B.

一个同事喜欢第一种forms,告诉他们是最优化的,宁愿重复使用一个声明。

我更喜欢第二个(并试图说服我的同事!-)),读到:

  • 它将variables的范围缩小到需要的地方,这是一件好事。
  • Java的优化足以使性能没有显着差异。 IIRC,也许第二种forms更快。

无论如何,它依赖于编译器和/或JVM的质量,属于过早优化的范畴。

如果你在lambda中使用这个variables,那么在C#中是有区别的。但是一般来说,编译器基本上会做同样的事情,假设variables只在循环中使用。

鉴于它们基本上是相同的:请注意,版本b使读者更加清楚,variables不是,也不能在循环之后使用。 另外, 版本b更容易重构。 在版本a中将循环体抽取到自己的方法中是比较困难的。 此外,版本B保证你没有这种重构的副作用。

因此,版本a让我感到恼火,因为没有任何好处,并且使代码的推理变得更加困难。

那么,你总是可以为此做一个范围:

 { //Or if(true) if the language doesn't support making scopes like this double intermediateResult; for (int i=0; i<1000; i++) { intermediateResult = i; System.out.println(intermediateResult); } } 

这样你只能声明一次这个variables,当你离开循环时它就会死掉。

我一直认为,如果你在你的循环中声明你的variables,那么你正在浪费内存。 如果你有这样的事情:

 for(;;) { Object o = new Object(); } 

那么不仅需要为每个迭代创build对象,还需要为每个对象分配一个新的引用。 看来,如果垃圾收集器很慢,那么你会有一堆悬而未决的引用需要清理。

但是,如果你有这个:

 Object o; for(;;) { o = new Object(); } 

然后你只创build一个单引用,并且每次都为它分配一个新的对象。 当然,它可能需要一点时间才能超出范围,但是只有一个悬而未决的参考。

我认为这取决于编译器,很难给出一个一般的答案。

我的做法如下:

  • 如果variables的types很简单(int,double,…)我更喜欢变体b (里面)。
    原因:减lessvariables的范围。

  • 如果variables的types不是简单的(某种classstruct ),我更喜欢变体a (外部)。
    原因:减less呼叫者的呼叫次数。

从性能的angular度来看,外部是(多)好。

 public static void outside() { double intermediateResult; for(int i=0; i < Integer.MAX_VALUE; i++){ intermediateResult = i; } } public static void inside() { for(int i=0; i < Integer.MAX_VALUE; i++){ double intermediateResult = i; } } 

我执行了两个函数每个10亿次。 outside()花了65毫秒。 inside()花了1.5秒。

这是更好的forms

 double intermediateResult; int i = byte.MinValue; for(; i < 1000; i++) { intermediateResult = i; System.out.println(intermediateResult); } 

1)以这种方式宣布一次既可变,也不一次循环。 2)分配它是更加肥美的所有其他选项。 3)所以最好的实践规则是迭代之外的任何声明。

A)是比B)安全的赌注………想象一下,如果你正在循环初始化结构而不是“int”或“float”,那么什么?

喜欢

 typedef struct loop_example{ JXTZ hi; // where JXTZ could be another type...say closed source lib // you include in Makefile }loop_example_struct; //then.... int j = 0; // declare here or face c99 error if in loop - depends on compiler setting for ( ;j++; ) { loop_example loop_object; // guess the result in memory heap? } 

你肯定会面临内存泄漏的问题! 因此,我认为'A'是更安全的赌注,而'B'易受内存累积的影响,尤其是在工作在closures源库的情况下。您可以在Linux上专门检测子工具'Helgrind'上的'Valgrind'工具。

这是一个有趣的问题。 从我的经验来看,当你为代码辩论这个问题时,需要考虑一个最终的问题:

有什么理由为什么variables需要全球?

仅在全局范围内声明variables是有意义的,而不是在本地多次声明该variables,因为它更好地组织代码并且需要更less的代码行。 但是,如果只需要在一个方法中进行本地声明,那么我将在该方法中对其进行初始化,因此很明显该variables与该方法完全相关。 如果你select后面的选项,注意不要在这个variables被初始化的方法之外调用这个variables – 你的代码将不知道你在说什么,并会报错。

另外,作为一个附注,不要在不同方法之间重复局部variables名称,即使它们的目的几乎相同。 它只是令人困惑。

如果有人感兴趣,我使用Node 4.0.0testingJS。 在循环之外声明导致平均超过1000次试验的性能提高〜5ms,每次试验循环次数为1亿次。 所以我会说继续,并以最易读/可维护的方式写入它是B,IMO。 我会把我的代码放在小提琴中,但是我使用了性能现在的Node模块。 代码如下:

 var now = require("../node_modules/performance-now") // declare vars inside loop function varInside(){ for(var i = 0; i < 100000000; i++){ var temp = i; var temp2 = i + 1; var temp3 = i + 2; } } // declare vars outside loop function varOutside(){ var temp; var temp2; var temp3; for(var i = 0; i < 100000000; i++){ temp = i temp2 = i + 1 temp3 = i + 2 } } // for computing average execution times var insideAvg = 0; var outsideAvg = 0; // run varInside a million times and average execution times for(var i = 0; i < 1000; i++){ var start = now() varInside() var end = now() insideAvg = (insideAvg + (end-start)) / 2 } // run varOutside a million times and average execution times for(var i = 0; i < 1000; i++){ var start = now() varOutside() var end = now() outsideAvg = (outsideAvg + (end-start)) / 2 } console.log('declared inside loop', insideAvg) console.log('declared outside loop', outsideAvg) 

即使我知道我的编译器足够聪明,我也不会依赖它,并会使用a)变体。

b)变体对我来说只有在你迫切需要在循环体之后使intermediateResult不可用时才有意义。 但无论如何,我无法想象这种绝望的情况。

编辑: 乔恩Skeet提出了一个很好的观点,表明循环内的variables声明可以使实际的语义差异。