1

次のように、二重でいくつかの簡単なテストを行っています。

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 値を渡します。

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

DoubleCalcTestJIT には副作用 (純粋な計算) がなく、結果が使用されないため、JIT は の実行を破棄します。効果がないため、ループ自体も最適化できます。

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 ms かかります。100000000 を 200000000 に変更すると、2 倍の時間がかかります。

したがって、結論は、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

これは、あなたが書いたのは value100000000ではなく、カンマ演算子で区切られた 3 つの値100, 000,000であり、両方のオペランドを評価し、右側の値を返すためです。ループに入ることはありません100,000,000 == (100,0),0 == 0,0 == 0

于 2013-01-25T02:08:57.007 に答える
1

最適化されている可能性がありますが、念のため、 に置き換えSystem.currentTimeMillisSystem.nanoTimeください。

System.nanoTimeはるかに正確なナノ秒単位で時間を返します。また、Javadocでは、経過時間を測定するために使用することを推奨しています。

于 2013-01-25T02:08:28.520 に答える
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);ていましたが、違いはなかったので削除しました。

ではd += DoubleCalcTest()、1,000,000回実行すると、約10ミリ秒です。これは、1 億回実行する時間の約 1/100 であり、誤差を考慮すると正しい値です。

于 2013-01-25T02:22:19.533 に答える
0

ゴードンには良い点があります。実際のコードにコンマを残しましたか?

そうでない場合は、JVM が全体を最適化できないように、メソッドに引数値を渡してみてください。

例えば ​​:

public static double DoubleCalcTest(double x) 
{

double y = 123.456789;

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

次に、ループのインデックスによってインクリメントされた double 値を渡します。JVM は、静的な計算ではなくなるため、それほど最適化できなくなります。

于 2013-01-25T02:15:55.317 に答える