我一直想知道,在一般情况下,在循环之前声明一个丢弃的变量,而不是在循环内部重复,使任何(性能)差异? 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);
}

哪个更好,a还是b?

我怀疑重复的变量声明(例b)在理论上会产生更多的开销,但编译器足够聪明,所以这无关紧要。例b的优点是更紧凑,并且将变量的作用域限制在使用它的地方。尽管如此,我还是倾向于根据示例a编写代码。

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


当前回答

我总是使用A(而不是依赖于编译器),也可能重写为:

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

这仍然将intermediateResult限制在循环的范围内,但不会在每次迭代期间重新声明。

其他回答

很长一段时间我都有同样的问题。所以我测试了一段更简单的代码。

结论:对于此类病例,两组间无显著性差异。

外环箱

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

内环箱

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

我检查了IntelliJ的反编译器上的编译文件,对于这两种情况,我得到了相同的Test.class

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

我还使用这个答案中给出的方法对这两种情况的代码进行了反汇编。我将只展示与答案相关的部分

外环箱

Code:
  stack=2, locals=3, args_size=1
     0: iconst_0
     1: istore_2
     2: iload_2
     3: sipush        1000
     6: if_icmpge     26
     9: iload_2
    10: iconst_2
    11: iadd
    12: istore_1
    13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
    16: iload_1
    17: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
    20: iinc          2, 1
    23: goto          2
    26: return
LocalVariableTable:
        Start  Length  Slot  Name   Signature
           13      13     1 intermediateResult   I
            2      24     2     i   I
            0      27     0  args   [Ljava/lang/String;

内环箱

Code:
      stack=2, locals=3, args_size=1
         0: iconst_0
         1: istore_1
         2: iload_1
         3: sipush        1000
         6: if_icmpge     26
         9: iload_1
        10: iconst_2
        11: iadd
        12: istore_2
        13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        16: iload_2
        17: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        20: iinc          1, 1
        23: goto          2
        26: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           13       7     2 intermediateResult   I
            2      24     1     i   I
            0      27     0  args   [Ljava/lang/String;

如果您仔细注意,在LocalVariableTable中,只有分配给i和intermediateResult的Slot会根据它们的出现顺序进行交换。slot中的相同差异也反映在其他代码行中。

当前无额外操作 在这两种情况下,intermediateResult仍然是一个局部变量,因此访问时间没有差异。

奖金

编译器做了大量的优化,看看在这种情况下发生了什么。

零工作情况

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

零工作反编译

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

我一直认为,如果你在循环中声明变量,那么你就是在浪费内存。如果你有这样的东西:

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

然后,不仅需要为每次迭代创建对象,还需要为每个对象分配一个新的引用。看起来,如果垃圾收集器很慢,那么就会有一堆需要清理的悬空引用。

然而,如果你有这样的情况:

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

然后,您只需创建一个引用,并每次为它分配一个新对象。当然,它可能需要更长的时间才能超出作用域,但这时只需要处理一个悬空引用。

以下是我在。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);
}

这是我从。net Reflector中得到的,当CIL被渲染回代码时。

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);
}

So both look exactly same after compilation. In managed languages code is converted into CL/byte code and at time of execution it's converted into machine language. So in machine language a double may not even be created on the stack. It may just be a register as code reflect that it is a temporary variable for WriteLine function. There are a whole set optimization rules just for loops. So the average guy shouldn't be worried about it, especially in managed languages. There are cases when you can optimize manage code, for example, if you have to concatenate a large number of strings using just string a; a+=anotherstring[i] vs using StringBuilder. There is very big difference in performance between both. There are a lot of such cases where the compiler cannot optimize your code, because it cannot figure out what is intended in a bigger scope. But it can pretty much optimize basic things for you.

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”是容易受到内存积累的影响,特别是工作在近源库中。你可以在Linux上使用“Valgrind”工具检查,特别是子工具“Helgrind”。

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

为什么变量需要是全局的?

It makes sense to only declare the variable once, globally, as opposed to many times locally, because it is better for organizing the code and requires less lines of code. However, if it only needs to be declared locally within one method, I would initialize it in that method so it is clear that the variable is exclusively relevant to that method. Be careful not to call this variable outside the method in which it is initialized if you choose the latter option--your code won't know what you're talking about and will report an error.

另外,作为旁注,不要在不同的方法之间重复局部变量名,即使它们的目的几乎相同;这让人很困惑。