8

低レイテンシ環境で実行される (Java) アプリケーションがあります。通常、命令は最大 600 マイクロ (+/- 100) で処理されます。当然のことながら、マイクロ秒の空間にさらに移行すると、レイテンシーの変化が見られます。現在、その時間の 2/3 が 2 つのコア ドメイン オブジェクトの割り当てに費やされていることに気付きました。

ベンチマークは、コードの問題のあるセクションを既存の参照から文字通りオブジェクトの構築に分離しました。つまり、基本的に参照のロード (各クラスで最大 15) といくつかのリストが新しく作成されましたが、正確に測定されるものについては以下の注を参照してください。ここ。

それぞれが一貫して約 100 マイクロ秒かかりますが、これは私には説明できません。その理由を突き止めようとしています。簡単なベンチマークでは、同様のサイズの文字列でいっぱいのオブジェクトが新しくなるまでに約 2 ~ 3 マイクロ秒かかることが示唆されています。

ここに 2 つの Q があります

  • この種の行動をどのように調査しますか?
  • 遅い割り当てにはどのような説明がありますか?

関連するハードウェアは、Sun X4600 上の Solaris 10 x86 であり、8* デュアル コア オプテロン @ 3.2GHz であることに注意してください。

私たちが見たものには、

  • PrintTLAB の統計情報を確認すると、低速な割り当てがほとんどないため、競合は発生しないはずです。
  • PrintCompilation は、これらのコードのビットの 1 つが JIT フレンドリーではないことを示唆していますが、Solaris はここでいくつかの異常な動作をしているようです (つまり、最新の Linux に対して、solaris10 と同様のヴィンテージの Linux を現在ベンチに置いていません)。
  • LogCompilation...控えめに言っても解析が少し難しいので、これは進行中の仕事であり、今のところ明らかなことは何もありません
  • JVM バージョン... 6u6 と 6u14 の間で一貫しており、6u18 または最新の 7 はまだ試していません

ありとあらゆる考えに感謝

物事を明確にするための、さまざまな投稿へのコメントの要約

  • 私が測定しているコストは、ビルダー (これらの1 つなど) を介して構築され、そのプライベート コンストラクターが new ArrayList を数回呼び出し、既存のオブジェクトへの参照を設定するオブジェクトを作成する総コストです。測定されたコストには、ビルダーのセットアップとビルダーのドメイン オブジェクトへの変換のコストが含まれます。
  • コンパイル (ホットスポットによる) には大きな影響がありますが、それでも比較的遅いです (この場合のコンパイルでは、100 マイクロ秒から 60 マイクロ秒までかかります)。
  • 私の単純なベンチマークでのコンパイル (ホットスポットによる) は、割り当て時間を ~2micros から ~300ns に短縮します
  • レイテンシーは、若い世代のコレクション アルゴリズム (ParNew または Parallel スカベンジ) によって変化しません。
4

5 に答える 5

3

あなたの質問は、「私の問題は何ですか」というよりも、問題を調査する方法に関するものだったので、いくつかのツールを試してみることにします。

何が起こっているのか、いつBTraceなのかをよりよく理解するための非常に便利なツールです。これは DTrace に似ていますが、純粋な Java ツールです。その点については、あなたが DTrace を知っていることを前提としています。これらにより、JVM と OS で何が起こっているか、いつ何が起こっているかについてある程度の可視性が得られます。

ああ、元の投稿で明確にする別のこと。どのコレクターを実行していますか? CMS のような低一時停止コレクターを使用していると、待ち時間が長い問題があると想定しています。もしそうなら、チューニングを試しましたか?

于 2009-11-18T13:45:36.290 に答える
3

同じタスクを何度も繰り返している場合、CPU は非常に効率的に実行される傾向があります。これは、キャッシュ ミス時間と CPU のウォームアップが要因として表示されないためです。JVM のウォーム タイムも考慮していない可能性もあります。

JVMやCPUがウォームアップされていないときに同じことを試してみると. 非常に異なる結果が得られます。

同じことを 25 回 (コンパイルのしきい値未満) 実行し、テスト間でスリープ (100) してみてください。実際のアプリケーションで見られるものに近い、はるかに高い時間が期待できます。

アプリの動作は異なりますが、私の主張を説明するためです。IO を待機することは、単純なスリープよりも混乱を招く可能性があることがわかりました。

ベンチマークを実行するときは、類似のものと比較していることを確認する必要があります。

import java.io.*;
import java.util.Date;

/**
Cold JVM with a Hot CPU took 123 us average
Cold JVM with a Cold CPU took 403 us average
Cold JVM with a Hot CPU took 314 us average
Cold JVM with a Cold CPU took 510 us average
Cold JVM with a Hot CPU took 316 us average
Cold JVM with a Cold CPU took 514 us average
Cold JVM with a Hot CPU took 315 us average
Cold JVM with a Cold CPU took 545 us average
Cold JVM with a Hot CPU took 321 us average
Cold JVM with a Cold CPU took 542 us average
Hot JVM with a Hot CPU took 44 us average
Hot JVM with a Cold CPU took 111 us average
Hot JVM with a Hot CPU took 32 us average
Hot JVM with a Cold CPU took 96 us average
Hot JVM with a Hot CPU took 26 us average
Hot JVM with a Cold CPU took 80 us average
Hot JVM with a Hot CPU took 26 us average
Hot JVM with a Cold CPU took 90 us average
Hot JVM with a Hot CPU took 25 us average
Hot JVM with a Cold CPU took 98 us average
 */
public class HotColdBenchmark {
    public static void main(String... args) {
        // load all the classes.
        performTest(null, 25, false);
        for (int i = 0; i < 5; i++) {
            // still pretty cold
            performTest("Cold JVM with a Hot CPU", 25, false);
            // still pretty cold
            performTest("Cold JVM with a Cold CPU", 25, true);
        }

        // warmup the JVM
        performTest(null, 10000, false);
        for (int i = 0; i < 5; i++) {
            // warmed up.
            performTest("Hot JVM with a Hot CPU", 25, false);
            // bit cold
            performTest("Hot JVM with a Cold CPU", 25, true);
        }
    }

    public static long performTest(String report, int n, boolean sleep) {
        long time = 0;
        long ret = 0;
        for (int i = 0; i < n; i++) {
            long start = System.nanoTime();
            try {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                ObjectOutputStream oos = new ObjectOutputStream(baos);
                oos.writeObject(new Date());
                oos.close();
                ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
                Date d = (Date) ois.readObject();
                ret += d.getTime();
                time += System.nanoTime() - start;
                if (sleep) Thread.sleep(100);
            } catch (Exception e) {
                throw new AssertionError(e);
            }
        }
        if (report != null) {
            System.out.printf("%s took %,d us average%n", report, time / n / 1000);
        }
        return ret;
    }
}
于 2010-01-06T21:01:09.183 に答える
2

メモリ割り当ては、副作用を引き起こす可能性があります。メモリ割り当てが原因でヒープが圧縮されている可能性はありますか? メモリ割り当てが原因で GC が同時に実行されているかどうかを確認しましたか?

新しい ArrayLists を作成するのにかかる時間を個別に計りましたか?

于 2009-11-18T11:06:42.403 に答える
2

そのような優れたハードウェアを使用しても、汎用 OS で実行される汎用 VM からマイクロ秒のレイテンシーの保証を期待することはおそらく期待できません。大規模なスループットは、期待できる最高のものです。リアルタイム VM が必要な場合は、リアルタイム VM に切り替えるのはどうですか (私は RTSJ などについて話しています...)

...私の2セント

于 2009-11-18T11:12:05.420 に答える
2

いくつかのワイルドな推測:

Java VM は、短期間のオブジェクトのメモリを長期間のオブジェクトとは異なる方法で処理することを理解しています。オブジェクトが 1 つの関数ローカル参照からグローバル ヒープへの参照を持つようになる時点で、大きなイベントになるのは理にかなっているように思えます。関数の終了時にクリーンアップできる代わりに、GC で追跡する必要があります。

または、単一のオブジェクトへの 1 つの参照から複数の参照に移行するために、GC アカウンティングを変更する必要がある場合もあります。オブジェクトが単一の参照を持っている限り、クリーンアップは簡単です。複数の参照に参照ループが含まれる可能性があり、GC は他のすべてのオブジェクトで参照を検索する必要がある場合があります。

于 2009-12-23T15:54:30.170 に答える