5

バックグラウンド

明示的なJavaダウンキャストのパフォーマンスを測定するためのコード(下部に投稿)を実行していて、少し異常な感じに遭遇しました...またはおそらく2つの異常です。

私はすでにJavaキャストのオーバーヘッドに関するこのスレッドを見てきましたが、この特定の現象ではなく、一般的なキャストについてのみ話しているように見えました。このスレッドは同様のトピックをカバーしており、時期尚早の最適化についてのアドバイスは本当に必要ありません。パフォーマンスを最大化するためにアプリケーションの一部を調整しているので、これは論理的なステップです。

テスト

基本的に、ダウンキャストのパフォーマンスと、sであるが、sとして入力された.toString()オブジェクトのメソッドをテストしたかったのです。そこで、同等のコンテンツでとを作成し、3つのループを実行して、タイミングを合わせました。StringObjectString aObject b

  • ループ1は((String) b).toLowerCase();
  • ループ2はb.toString().toLowerCase();
  • ループ3はa.toLowerCase()です。

試験結果

(ミリ秒単位の測定値。)

   iters   |  Test Round  |  Loop 1  |  Loop 2  |  Loop 3
-----------|--------------|----------|----------|----------
50,000,000 |      1       |   3367   |   3166   |   3186
  Test A   |      2       |   3543   |   3158   |   3156
           |      3       |   3365   |   3155   |   3169
-----------|--------------|----------|----------|----------
 5,000,000 |      1       |    373   |    348   |    369
  Test B   |      2       |    373   |    348   |    370
           |      3       |    399   |    334   |    371
-----------|--------------|----------|----------|----------
  500,000  |      1       |    66    |    36    |    33
  Test C   |      2       |    71    |    36    |    41
           |      3       |    66    |    35    |    34
-----------|--------------|----------|----------|----------
  50,000   |      1       |    27    |     5    |     5
  Test D   |      2       |    27    |     6    |     5
           |      3       |    26    |     5    |     5
-----------|--------------|----------|----------|----------

テストに使用されるコード

long t, iters = ...;

String a = "String", c;
Object b = "String";

t = System.currentTimeMillis();
for (int i = 0; i < iters; i++) {
    c = ((String) b).toLowerCase();
}
System.out.println(System.currentTimeMillis() - t);

t = System.currentTimeMillis();
for (int i = 0; i < iters; i++) {
    c = b.toString().toLowerCase();
}
System.out.println(System.currentTimeMillis() - t);

t = System.currentTimeMillis();
for (int i = 0; i < iters; i++) {
    c = a.toLowerCase();
}
System.out.println(System.currentTimeMillis() - t);

最後に、質問

私が最も魅力的だと思うのは、ループ2(.toString())が3つのうち(特にテストBで)最高のパフォーマンスを発揮しているように見えたことです。これは直感的には意味がありません。なぜ、すでにオブジェクトを持っているよりも呼び出しが速いのでしょうか?.toString()String

私を悩ませているもう一つのことは、それがスケーリングしないということです。テストAとDを比較すると、互いに比較すると9倍ずれています(27 * 1000 = 27000、3000ではありません)。反復回数が増えるにつれて、なぜこの大きな不一致があるのでしょうか。

これらの2つの異常が真実であることが証明された理由について誰かが説明を提供できますか?

(奇妙な)現実

更新:Bruno Reisソリューションのアドバイスを受けて、コンパイラ出力を使用してベンチマークを再度実行しました。最初のループは初期化のものでいっぱいだったので、これを行うために「ジャンク」ループを入れました。それが行われると、結果は予想に近づ​​きました。

これは、5,000,000回の反復を使用したコンソールからの完全な出力です(私がコメントしました)。

     50    1             java.lang.String::toLowerCase (472 bytes)
     50    2             java.lang.CharacterData::of (120 bytes)
     53    3             java.lang.CharacterDataLatin1::getProperties (11 bytes)
     53    4             java.lang.Character::toLowerCase (9 bytes)
     54    5             java.lang.CharacterDataLatin1::toLowerCase (39 bytes)
     67    6     n       java.lang.System::arraycopy (0 bytes)   (static)
     68    7             java.lang.Math::min (11 bytes)
     68    8             java.util.Arrays::copyOfRange (63 bytes)
     69    9             java.lang.String::toLowerCase (8 bytes)
     69   10             java.util.Locale::getDefault (13 bytes)
     70    1 %           Main::main @ 14 (175 bytes)
[GC 49088K->360K(188032K), 0.0007670 secs]
[GC 49448K->360K(188032K), 0.0024814 secs]
[GC 49448K->328K(188032K), 0.0005422 secs]
[GC 49416K->328K(237120K), 0.0007519 secs]
[GC 98504K->352K(237120K), 0.0122388 secs]
[GC 98528K->352K(327552K), 0.0005734 secs]
    595    1 %           Main::main @ -2 (175 bytes)   made not entrant
548 /****** Junk Loop ******/
    597    2 %           Main::main @ 61 (175 bytes)
[GC 196704K->356K(327552K), 0.0008460 secs]
[GC 196708K->388K(523968K), 0.0005100 secs]
343 /****** Loop 1 ******/
    939    2 %           Main::main @ -2 (175 bytes)   made not entrant
    940   11             java.lang.String::toString (2 bytes)
    940    3 %           Main::main @ 103 (175 bytes)
[GC 393092K->356K(523968K), 0.0036496 secs]
377 /****** Loop 2 ******/
   1316    3 %           Main::main @ -2 (175 bytes)   made not entrant
   1317    4 %           Main::main @ 145 (175 bytes)
[GC 393060K->332K(759680K), 0.0008326 secs]
320 /****** Loop 3 ******/
4

2 に答える 2

7

SOや他の場所でのほとんどの質問がJavaコードのベンチマークに関連しているため、ベンチマークには欠陥があります。JITコンパイル方法、HotSpot最適化ループなど、想像よりもはるかに多くのことを測定しています。

http://www.ibm.com/developerworks/java/library/j-jtp02225/index.htmlを確認してください。

また、サーバーVMとクライアントVMの動作は異なります(JVMはクライアントでの起動は速くなりますが、コンパイル中にバイトコードの解釈を開始し、サーバーVMは実行前にコンパイルするため、しばらくの間実行が遅くなります)など。

GCも干渉している可能性があります。ベンチマーク中にフルGCを取得した場合(通常、フルGCは他のすべてのスレッドを完全に一時停止します)。マイナーなコレクションでさえ、ループ内に作成された可能性のある巨大な混乱をクリーンアップするためにかなりのCPUを使用できるため、ある程度の影響を与える可能性があります。

適切なベンチマークを実行するには、JVMを「ウォームアップ」し、JVMからの多くの出力をオンにして、測定対象を確認する必要があります。

ここでSOに関するこの質問を確認してください。これには、Javaでベンチマークを作成する方法が記載されており、上記のトピックやさらに詳細なトピックが含まれています。Javaで正しいマイクロベンチマークを作成するにはどうすればよいですか。

于 2013-01-23T03:47:12.520 に答える
2

.toString()を呼び出す方が、すでにStringオブジェクトを持っているよりも速いのはなぜですか?

数値を見ると、ループ2がループ3よりも一貫して高速であることがまったくわかりません。実際、場合によっては遅くなります。テストBの明らかな重要な違いは、ループ2の場合よりもループ3の場合の方がGCがもう一度実行されることです。これは、単にベンチマーク設計の成果物である可能性があります。

とにかく、(もしあれば)何が起こっているのかを本当に知りたいのであれば、それぞれの場合にJITコンパイラーによって生成されるネイティブ命令を調べる必要があります。(これを行うためのJVMオプションがあります...)

于 2013-01-23T04:55:37.983 に答える