Java Microbenchmark Harness(JMH)の最新バージョンには、新しいプロファイラーdtraceasm
。 待望の Mac OS Xのポートperfasm
ベンチマークのアセンブラプロファイルを表示できます。
簡単な世論調査では、入力でJavaメソッドを受け取り、コンパイルされたメソッドのアセンブラーリストと最もホットな指示、それらの分布、および「仮想マシンはさらに5% Symbol::as_C_string(char*, int)
メソッド。
perfasm
を移植する過程で、実際にはすべてが非常に複雑ではなく、そのようなプロファイラーがどのように配置されているかを伝えたいという要望がありました。
この記事を理解するには、たとえばその使用例を見て、JMHに慣れることを強くお勧めします。
はじめに
そのようなプロファイラーは何をすべきですか?
Javaベンチマークの場合、生成されたコードのレベルでプロセッサー時間が最も費やされる場所を正確に示す必要があります。
同時に、通常は生成されたコードが大量にあるため、プロファイラーの出力で必要な情報を探す必要がないように、非常に正確にこれを行うことができるはずです。
たとえば、対数を数えるメソッドの場合:
@Benchmark public double log(double x) { return Math.log(x); }
dtraceasm
またはperfasm
は、左のスクリーンショットのようにプロファイルを表示し、 fstpl
命令を非難しfstpl
。 現代のプロセッサの強力なパイプライン化により、このようなプロファイルは誤っている可能性があり、多くの場合、ホットと見なされる命令だけでなく前の命令も参照するのが理にかなっています。 ここでは、対数をカウントするfyl2x
です。
実際、このようなプロファイラーはperf annotate
に非常に似ていますが、JITでコンパイルされたJavaコードで動作します。
なんで?
また、JITコンパイラーを作成していない場合、 *asm
プロファイラーが必要なのはなぜですか? 最後になりましたが、もちろん、次の質問に非常に迅速に答えるのに役立つため、好奇心からです。
- そして、私のメソッドは何にコンパイルされましたか? (もちろん、
PrintAssembly
出力に移動して適切な場所を見つけるか、 JITWatchを使用できますが、これは通常あまり便利ではありません) - JITコンパイラーはどのような最適化を行うことができますか、できませんか、それを裏切るか混乱させることは可能ですか?
- ガベージコレクターを変更すると、生成されたコードはどのように変わりますか?
- 自分自身の現実の認識(「
Math.sqrt
はこの方法では間違いなく遅くなります」)は、厳しい現実とどの程度異なりますか - そして、なぜコードは同じコードよりも一方向で速く書かれていますが、少し異なって書かれていますか?
好奇心に加えて、たとえばスレッドセーフキューや高度に特殊化されたclassなど、小さな場所を突然最適化することに決めた場合にも同じ質問に答えられると便利です。
さて、このツールを使用する場合は、ツールが何らかの魔法であると認識されず、その機能と制限を理解できないように、少なくとも内部でどのように配置されているかを大まかに理解しておくと役立ちます。
プリントアッセンブリー
生成されたコードを使用してプロファイルを作成するには、まずどこかからこの生成されたコードを取得する必要があります でもお金がありません 。 幸いなことに、すべてがすでに発明されており、仮想マシン(以下、ホットスポットのみ)はコンパイル済みのすべてのコードをstdoutで印刷できます-XX:+PrintAssembly
必要なフラグ( -XX:+PrintAssembly
)を有効にし、特別な逆アセンブラを$JAVA_HOME
入れるだけです。 これを行う方法はインターネット上で十分な説明があります。通常、自分で何かを収集する必要はなく、プラットフォーム用に組み立てられた逆アセンブラをダウンロードするだけです。
PrintAssemblyは便利ですが、最も便利ではありません。 出力にはよく知られた形式があり、現在の行が参照しているバイトコード命令、現在印刷されているメソッド、または引数が含まれているレジスタについてコメントが付けられていますが、メガバイト単位で測定され、コンパイルされたメソッド(C1コンパイラ、 C2コンパイラ、最適化解除後のバージョン、GOTO 1)。そのため、通常、その中から目的のものを見つけることは非常に困難です。
この巨大な結論のプロファイラーは、ベンチマークの最もホットな部分を見るためにどこを見る必要があるかを正確に示しているはずです。 そして、このようなプロファイラを作成するために、この結論では、どの命令方法がメモリ内のどのアドレスを参照するかについての情報と、オプションで逆アセンブラからのコメントに興味があります。
Dtrace
DTraceは、Solaris、FreeBSD、Mac OS X、および一部Linuxでサポートされている動的トレースフレームワークです。 カーネルモジュールで構成され、主要な機能とクライアントプログラムを特別な言語Dで実装します( 他の言語Dと混同しないでください)。 クライアントプログラムは、どのイベントに関心があるかを宣言し、カーネルモジュールはプログラムを特別なバイトコードにコンパイルし、準備作業を実行し、必要に応じてこのプログラムの実行を開始します。 同時に、D言語は安全であり、無限ループに陥ったり、アプリケーションをペイントしたりするなど、あまり拡張することはできません。そのため、D言語のプログラムはカーネルで直接実行できます。 フレームワーク自体は非常に強力であり、この記事の範囲を超えて非常に興味深く、自明でないことをたくさん行うことができます; dtraceasm
必要な機能のみを検討します。
dtraceasm
はprofile-n
イベントプロバイダーを使用しprofile-n
。これは特別なイベントではハングせず、一定の間隔で単純にユーザープログラムを呼び出します。
メカニズムは単純で、カーネルは特定の頻度でタイマーを登録し、現在CPUで実行されているプロセスへの割り込みを開始し、ハンドラーでDTraceスクリプトを呼び出します。
スクリプト自体は次のとおりです。
profile-1001 /arg1/ { printf("%d 0x%lx %d", pid, arg1, timestamp); ufunc(arg1)}
「1秒あたり1001回、現在の実行可能プロセスのpid、そのPC、現在の時刻、およびプロセスがユーザー空間で現在実行されている場合は実行可能メソッドの名前(ライブラリ名とともに)を入力します。」
PC( プログラムカウンター )は、現在実行中の命令のアドレスを含む特殊レジスターです。 しかし、メソッドの名前はどこから来たのでしょうか?
カーネルは、ロードされたライブラリ、実行可能ファイルとそのシンボル(メソッドはシンボル)、およびロードされたアドレスに関するすべてを知っているため、この知識を使用してインデックス「命令アドレス->ライブラリ->特定のメソッド」を構築できます。 つまり、PCの価値がわかれば、この命令がどこから来たかを知ることができます。
例
lib.so
ライブラリlib.so
は、 foo()
メソッドはオフセット1024
で始まり、次のbar()
メソッドはオフセット2048
で始まり、ライブラリ自体は1048576
プロセスにロードされます。 PCの現在の値が[1048576 + 1024, 1048576 + 2048]
lib.so
[1048576 + 1024, 1048576 + 2048]
の範囲にある場合、 lib.so
foo()
メソッドlib.so
ます。
しかし、コードが動的にロードされ(実際、JITコンパイラーが行うこと)、シンボルに関する情報がない場合、カーネルはメソッドの名前を見つけられません。
水を加えるだけ
注釈付きアセンブラベンチマークを今すぐ取得する方法は?
ベンチマークはPrintAssembly
フラグを使用して別のJVMで起動され、起動直後にDTraceスクリプトが起動され、その結果がファイルに書き込まれます。
これらのデータを手元に置いておくと、次の手順を実行するだけです。
- pidでフィルタリングされたDTrace排気とベンチマーク測定の反復時間
- シンボル名のない行は、PrintAssembly出力からの命令のアドレスと組み合わされ、
inc %r10d
という形式の文字列表現と、inc %r10d
からのオプションのコメントを取得します。 - 結果は、同一の線が崩壊し、それらの頻度のカウンターが巻き付けられるプロファイルに集約されます
- プロファイル内のいくつかのヒューリスティックの助けを借りて、「ホット」命令の連続領域があります。 たとえば、領域内の命令が全体でプロファイル全体の10%を占め、隣接する命令を追加すると、領域の重みに意味のない数が追加される場合、それを調べることに関心があると仮定できます。
- ネイティブメソッドは、頻度でソートされた別のプロファイル「ホットメソッド」に分類されます。
- 結果はきれいにフォーマットされ、コンソールでユーザーに送信されます
同時に、PrintAssemblyの出力には、C1コンパイラーとC2自体の両方によってコンパイルされたコードが含まれるという問題がなくなります。これは、ウォームアップの反復後にコンパイルされたコードの1つのバージョンのみがホットプロファイルに入るためです(安定した状態のベンチマークがない場合)一定の再コンパイルが行われ、さらに*asm
プロファイラーはウォームアップ反復からイベントをフィルターします)、ネイティブメソッド(JVM自体の内部、ネイティブ呼び出しなど)はホットメソッドのトップになります。
注: Mac OS Xへの移植の観点からは、DTraceを使用してPCサンプリングを行うだけでperfasm
結果を処理するための残りのインフラストラクチャは、 perfasm
の時代から存在し、この記事の著者は何もしていません(I)。
おわりに
シンプルなツールの組み合わせを使用すると、準備のできていない開発者にとってはブラックボックスのように見える非常に強力なプロファイラーが得られ、それが実際にどのように機能し、魔法やロケット科学がまったくないことがわかります(そして、その仕組みを簡単に理解できます)例: perfasm
)。
記事のすべての不正確さを読んで、ごみではなく大衆に知識を伝えることを確認してくれたAlexei Shipilevに感謝します:)