Java 在循环之前或循环中声明变量之间的区别?

Java 在循环之前或循环中声明变量之间的区别?,java,performance,loops,variables,initialization,Java,Performance,Loops,Variables,Initialization,我一直想知道,一般来说,在循环之前声明一个丢弃变量,而不是在循环内部重复声明,是否会产生任何(性能)差异? Java中的一个(毫无意义的)示例: a)循环前声明: double intermediateResult; for(int i=0; i < 1000; i++){ intermediateResult = i; System.out.println(intermediateResult); } 双中间结果; 对于(int i=0;i

我一直想知道,一般来说,在循环之前声明一个丢弃变量,而不是在循环内部重复声明,是否会产生任何(性能)差异? Java中的一个(毫无意义的)示例:

a)循环前声明:

double intermediateResult;
for(int i=0; i < 1000; i++){
    intermediateResult = i;
    System.out.println(intermediateResult);
}
双中间结果;
对于(int i=0;i<1000;i++){
中间结果=i;
系统输出打印项次(中间结果);
}
b)循环内的声明(重复):

for(int i=0; i < 1000; i++){
    double intermediateResult = i;
    System.out.println(intermediateResult);
}
for(int i=0;i<1000;i++){
双中间结果=i;
系统输出打印项次(中间结果);
}
哪个更好,a还是b

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


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

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

它依赖于语言-IIRC C优化了这一点,因此没有任何区别,但JavaScript(例如)每次都会进行整个内存分配。

即使我知道我的编译器足够聪明,我也不想依赖它,而是会使用a)变量

b)只有当您迫切需要在循环体之后使中间结果不可用时,变体才对我有意义。但无论如何,我无法想象如此绝望的局面


编辑:Jon Skeet提出了一个非常好的观点,表明循环中的变量声明可以产生实际的语义差异。

我怀疑一些编译器可以将两者优化为相同的代码,但肯定不是全部。所以我想说你和前者相处得更好。使用后者的唯一原因是,如果您希望确保所声明的变量仅在循环中使用。

一般来说,我在最可能的内部范围内声明变量。因此,如果您没有在循环之外使用intermediateResult,那么我会选择B。

这取决于语言和确切用法。例如,在C#1中,这没有什么区别。在C#2中,如果局部变量由匿名方法(或C#3中的lambda表达式)捕获,则会产生非常显著的差异

例如:

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

不同之处在于,所有动作都捕获相同的
外部变量,但每个动作都有自己独立的
内部变量。

在我看来,b是更好的结构。在a中,intermediateResult的最后一个值在循环完成后保持不变

编辑:
这与值类型没有太大区别,但引用类型可能有点重要。就我个人而言,我喜欢尽快取消对变量的引用以进行清理,b会为您这样做,哪个更好,a还是b


从性能的角度来看,您必须对其进行测量。(在我看来,如果你能测量一个差异,编译器就不是很好)

从维护角度来看,b更好。在尽可能狭窄的范围内,在同一位置声明和初始化变量。不要在声明和初始化之间留下漏洞,也不要污染您不需要的名称空间。

我将始终使用(而不是依赖编译器)并可能重写为:

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

for(int i=0,double intermediateResult=0;i这是VB.NET中的一个问题。在本例中,Visual Basic结果不会重新初始化变量:

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 Basic变量具有默认值!),但此后每次都会打印
i

但是,如果您添加一个
=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...

我分别运行了20次A和B示例,循环了1亿次。(JVM-1.5.0)

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

B:平均执行时间:.067秒

令我惊讶的是,B的速度稍微快一点。 像现在的计算机一样快,很难说你是否能准确地测量它。
我也会用这种方式编写代码,但我想说这并不重要。

一位同事更喜欢第一种形式,告诉他这是一种优化,更喜欢重复使用声明

我更喜欢第二个(并试着说服我的同事!;-)读过:

  • 它将变量的范围缩小到需要它们的地方,这是一件好事
  • Java进行了充分的优化,在性能上没有显著差异。IIRC,也许第二种形式更快

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

以下是我在.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);
}
doubler0;
对于(int i=0;i<1000;i++){
r0=i*i;
控制台写入线(r0);
}
对于(int j=0;j<1000;j++){
双r1=j*j;
控制台写入线(r1);
}
这就是我从渲染回代码时得到的结果

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);
}
for(int i=0;i<0x3e8;i++)
{
双r0=i*i;
控制台写入线(r0);
}
对于(int j=0;j<0x3e8;j++)
{
双r1=j*j;
控制台写入线(r1);
}
因此,在编译之后,两者看起来完全相同。在托管语言中,代码转换为CL/字节代码,在执行时转换为机器语言。因此,在机器语言中,甚至可能不会在堆栈上创建double。它可能只是一个寄存器,因为代码反映它是
WriteLine
函数的临时变量。对于循环,有一整套优化规则。所以普通人不应该担心,尤其是在托管语言中。在某些情况下,您可以优化管理代码,例如,如果您
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);
}
for(;;) {
  Object o = new Object();
}
Object o;
for(;;) {
  o = new Object();
}
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?
}
{ //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);
    }
}
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;
    }
}
int b;
for (int i = 0; i < 10; i++) {
    b = i;
}
for (int i = 0; i < 10; i++) {
    int b = i;
}
   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
   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 
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)
double intermediateResult;
int i = byte.MinValue;

for(; i < 1000; i++)
{
intermediateResult = i;
System.out.println(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);
}
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;
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);
}