1

我正在用 double 做一些简单的测试,如下所示:

startTime = System.currentTimeMillis();

for (int i = 0; i < 100_000_000; i++) 
{
   doubleCalcTest();
}

endTime = System.currentTimeMillis();    
System.out.println("That took " + (endTime - startTime) + " nanoseconds");

.

public static double doubleCalcTest() 
{
    double x = 987.654321;
    double y = 123.456789;

    x = x + y;
    x = x - y;
    x = x * y;
    return x / y;
}

事实证明,输出是 0 毫秒。这对我来说没有意义,因为如果我将 for 循环设置为仅运行 100,000 次,则输出为 3 毫秒。我发现 int 也以同样的方式行事。

任何人都可以帮我解决这个问题吗?谢谢。

我将代码更改为调用“System.nanoTime”时间,并按照建议传入一个按循环索引递增的双精度值。

double x = 123.456789
startTime = System.nanoTime();

for (int i = 0; i < 100_000_000; i++) 
{
   x = x + i;
   doubleCalcTest(x);
}

endTime = System.nanoTime();    
System.out.println("That took " + (endTime - startTime) + " nanoseconds");

.

public static double doubleCalcTest(double x) 
{
    double y = 123.456789;

    x = x + y;
    x = x - y;
    x = x * y;
    return x / y;
}

运行 10,000 次耗时 503,200 纳秒

运行 10,000,000 次耗时 3,421 纳秒

4

5 回答 5

7

JIT 放弃执行,DoubleCalcTest因为它没有任何副作用(纯计算)并且不使用结果。循环本身也可以优化,因为没有效果。

关闭 JIT 试试这个,大约需要 8000 毫秒:

java -Xint snippet.Snippet

在 byteocde 级别,没有任何优化。

javap -c snippet.Snippet

结果:

public class snippet.Snippet {
  public snippet.Snippet();
    Code:
       0: aload_0       
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return        

  public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #16                 // Method java/lang/System.currentTimeMillis:()J
       3: lstore_1      
       4: iconst_0      
       5: istore_3      
       6: goto          16
       9: invokestatic  #22                 // Method DoubleCalcTest:()D
      12: pop2          
      13: iinc          3, 1
      16: iload_3       
      17: ldc           #26                 // int 100000000
      19: if_icmplt     9
      22: invokestatic  #16                 // Method java/lang/System.currentTimeMillis:()J
      25: lstore_3      
      26: getstatic     #27                 // Field java/lang/System.out:Ljava/io/PrintStream;
      29: new           #31                 // class java/lang/StringBuilder
      32: dup           
      33: ldc           #33                 // String That took 
      35: invokespecial #35                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
      38: lload_3       
      39: lload_1       
      40: lsub          
      41: invokevirtual #38                 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
      44: ldc           #42                 // String  milliseconds
      46: invokevirtual #44                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      49: invokevirtual #47                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      52: invokevirtual #51                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      55: return        

  public static double DoubleCalcTest();
    Code:
       0: ldc2_w        #64                 // double 987.654321d
       3: dstore_0      
       4: ldc2_w        #66                 // double 123.456789d
       7: dstore_2      
       8: dload_0       
       9: dload_2       
      10: dadd          
      11: dstore_0      
      12: dload_0       
      13: dload_2       
      14: dsub          
      15: dstore_0      
      16: dload_0       
      17: dload_2       
      18: dmul          
      19: dstore_0      
      20: dload_0       
      21: dload_2       
      22: ddiv          
      23: dreturn       
}

如果您尝试通过将 DoubleCalc() 的结果分配给一个变量并在之后打印它来使用它。

public static void main(String[] args) {
    long startTime = System.currentTimeMillis();

    double res = 0;
    for (int i = 0; i < 100000000; i++) {
        res = DoubleCalcTest();
    }

    System.out.println(res);
    long endTime = System.currentTimeMillis();
    System.out.println("That took " + (endTime - startTime) + " milliseconds");
}

这将需要同样的时间。为什么?JIT 似乎足够聪明,可以理解结果不取决于迭代完成的次数。

但是,如果您将其更改为:

public static void main(String[] args) {
    long startTime = System.currentTimeMillis();

    double res = 0;
    for (int i = 0; i < 100000000; i++) {
        res += DoubleCalcTest();
    }

    System.out.println(res);
    long endTime = System.currentTimeMillis();
    System.out.println("That took " + (endTime - startTime) + " milliseconds");
}

结果取决于迭代次数,JIT 不会进一步优化。在这种情况下,大约需要 100 毫秒。如果我将 100000000 更改为 200000000,则需要两倍的时间。

所以结论是 JIT 停在那里。

笔记:

对于给 C 程序:

#include <stdio.h>

int main(int argc, char** argv) {

    long x = 0;
    int i;

    for(i=0; i<1000000; i++) {
       x+=i;
    }
    printf("%ld", x);
}

GCC 能够完全优化循环并在编译时计算 x 的值:

gcc -O2 -S main.c

电源:

    .file   "main.c"
    .section    .rodata.str1.1,"aMS",@progbits,1
.LC0:
    .string "%ld"
    .section    .text.startup,"ax",@progbits
    .p2align 4,,15
    .globl  main
    .type   main, @function
main:
.LFB11:
    .cfi_startproc
    movabsq $499999500000, %rsi   <---- See, this is the pre-computed result
    movl    $.LC0, %edi
    xorl    %eax, %eax
    jmp printf
    .cfi_endproc
.LFE11:
    .size   main, .-main
    .ident  "GCC: (GNU) 4.7.2 20121109 (Red Hat 4.7.2-8)"
    .section    .note.GNU-stack,"",@progbits

很酷吧?

于 2013-01-25T02:51:46.067 回答
6

这是因为您编写的不是 value 100000000,而是由逗号运算符分隔的三个值100, 000, 000,它计算其两个操作数,并返回右侧一个的值。您的循环永远不会被输入,因为100,000,000 == (100,0),0 == 0,0 == 0.

于 2013-01-25T02:08:57.007 回答
1

可能已经进行了优化,但可以肯定的是,替换System.currentTimeMillisSystem.nanoTime.

System.nanoTime以纳秒为单位返回时间,这要精确得多。此外,Javadocs建议使用它来测量经过的时间。

于 2013-01-25T02:08:28.520 回答
0

戈登的观点很好。您是否在实际代码中留下了逗号?

如果不是,请尝试将参数值传递给该方法,以便 JVM 无法优化整个事物。

例如 :

public static double DoubleCalcTest(double x) 
{

double y = 123.456789;

    x = x + y;
    x = x - y;
    x = x * y;
    return x / y;
}

然后传入一个按循环索引递增的双精度值。JVM 将无法优化,因为它不再是静态计算。

于 2013-01-25T02:15:55.317 回答
0

好的。我认为编译器发现你从不使用方法的返回值,所以方法根本没有执行。我的结果似乎与您的不同,但更有意义。

public static void main (String args[]) {
    long startTime = System.currentTimeMillis();
    double d = 0;

    for (int i = 0; i < 100000000; i++) {
        //DoubleCalcTest();
    }

    long endTime = System.currentTimeMillis();
    System.out.println("That took " + (endTime - startTime) + " milliseconds");
}

如我所说,在循环中取消注释DoubleCalcTest();,结果约为 80 毫秒。评论它,结果是一样的。

现在将其更改为d += DoubleCalcTest();结果为 844 毫秒。

之前我想在记录 endTime 之后应该使用 d ,例如 call System.out.println(d);,但它没有区别,所以我删除了它。

运行 1,000,000 次,d += DoubleCalcTest()大约需要 10 毫秒。这大约是运行 100,000,000 次时间的 1/100,考虑到错误,这是正确的。

于 2013-01-25T02:22:19.533 回答