V8のサンプリングベースのプロファイラの使用
V8には、サンプリングベースのプロファイリング機能が組み込まれています。プロファイリングはデフォルトで無効になっていますが、--prof
コマンドラインオプションで有効にすることができます。サンプラーは、JavaScriptとC/C++コードの両方のスタックを記録します。
ビルド #
GNを使ったビルドの手順に従って、d8
シェルをビルドします。
コマンドライン #
プロファイリングを開始するには、--prof
オプションを使用します。プロファイリングを行うと、V8はプロファイリングデータを含むv8.log
ファイルを生成します。
Windows
build\Release\d8 --prof script.js
その他のプラットフォーム (x64
ビルドをプロファイリングする場合は、ia32
をx64
に置き換えてください)
out/ia32.release/d8 --prof script.js
生成された出力の処理 #
ログファイルの処理は、d8シェルで実行されるJSスクリプトを使用して行われます。これが機能するためには、d8
バイナリ(またはシンボリックリンク、Windowsではd8.exe
)がV8チェックアウトのルート、または環境変数D8_PATH
で指定されたパスに存在する必要があります。注:このバイナリはログの処理にのみ使用され、実際プロファイリングには使用されないため、バージョンなどは関係ありません。
解析に使用するd8
がis_component_build
でビルドされていないことを確認してください!
Windows
tools\windows-tick-processor.bat v8.log
Linux
tools/linux-tick-processor v8.log
macOS
tools/mac-tick-processor v8.log
--prof
のWeb UI #
--preprocess
を使用してログを前処理します(C++シンボルを解決するためなど)。
$V8_PATH/tools/linux-tick-processor --preprocess > v8.json
ブラウザでtools/profview/index.html
を開き、そこでv8.json
ファイルを選択します。
出力例 #
Statistical profiling result from benchmarks\v8.log, (4192 ticks, 0 unaccounted, 0 excluded).
[Shared libraries]:
ticks total nonlib name
9 0.2% 0.0% C:\WINDOWS\system32\ntdll.dll
2 0.0% 0.0% C:\WINDOWS\system32\kernel32.dll
[JavaScript]:
ticks total nonlib name
741 17.7% 17.7% LazyCompile: am3 crypto.js:108
113 2.7% 2.7% LazyCompile: Scheduler.schedule richards.js:188
103 2.5% 2.5% LazyCompile: rewrite_nboyer earley-boyer.js:3604
103 2.5% 2.5% LazyCompile: TaskControlBlock.run richards.js:324
96 2.3% 2.3% Builtin: JSConstructCall
...
[C++]:
ticks total nonlib name
94 2.2% 2.2% v8::internal::ScavengeVisitor::VisitPointers
33 0.8% 0.8% v8::internal::SweepSpace
32 0.8% 0.8% v8::internal::Heap::MigrateObject
30 0.7% 0.7% v8::internal::Heap::AllocateArgumentsObject
...
[GC]:
ticks total nonlib name
458 10.9%
[Bottom up (heavy) profile]:
Note: percentage shows a share of a particular caller in the total
amount of its parent calls.
Callers occupying less than 2.0% are not shown.
ticks parent name
741 17.7% LazyCompile: am3 crypto.js:108
449 60.6% LazyCompile: montReduce crypto.js:583
393 87.5% LazyCompile: montSqrTo crypto.js:603
212 53.9% LazyCompile: bnpExp crypto.js:621
212 100.0% LazyCompile: bnModPowInt crypto.js:634
212 100.0% LazyCompile: RSADoPublic crypto.js:1521
181 46.1% LazyCompile: bnModPow crypto.js:1098
181 100.0% LazyCompile: RSADoPrivate crypto.js:1628
...
Webアプリケーションのプロファイリング #
今日の高度に最適化された仮想マシンは、Webアプリを驚異的な速度で実行できます。しかし、優れたパフォーマンスを実現するために、仮想マシンだけに頼るべきではありません。注意深く最適化されたアルゴリズムや、負荷の少ない関数は、多くの場合、すべてのブラウザで何倍もの速度向上を実現できます。Chrome DevToolsのCPUプロファイラは、コードのボトルネックを分析するのに役立ちます。しかし、時には、より深く、より詳細な分析が必要になることがあります。そこで、V8の内部プロファイラが役立ちます。
このプロファイラを使用して、MicrosoftがIE10と共にリリースしたマンデルブロエクスプローラーのデモを調べてみましょう。デモのリリース後、V8は計算を不必要に遅くしていたバグを修正し(そのため、デモのブログ記事ではChromeのパフォーマンスが低かった)、エンジンをさらに最適化し、標準システムライブラリが提供するものよりも高速なexp()
近似を実装しました。これらの変更により、**デモはChromeで以前測定されたよりも8倍高速に実行されました**。
しかし、すべてのブラウザでコードをより高速に実行したい場合はどうでしょうか?まず、**CPUをビジー状態にしているものを理解する**必要があります。Chrome(WindowsおよびLinux Canary)を次のコマンドラインスイッチで実行します。これにより、指定したURL(この場合はWebワーカーを使用しないマンデルブロデモのローカルバージョン)のプロファイラティック情報がv8.log
ファイルに出力されます。
./chrome --js-flags='--prof' --no-sandbox 'https://#:8080/'
テストケースを準備する際には、読み込み後すぐに作業が開始されるようにし、計算が完了したらChromeを閉じます(Alt + F4を押します)。これにより、ログファイルに必要なティックのみが記録されます。また、Webワーカーはこの手法ではまだ正しくプロファイリングされていないことに注意してください。
次に、V8に付属のtick-processor
スクリプト(または新しい実用的なWebバージョン)を使用して、v8.log
ファイルを処理します。
v8/tools/linux-tick-processor v8.log
注目すべき処理済み出力の興味深いスニペットを次に示します。
Statistical profiling result from null, (14306 ticks, 0 unaccounted, 0 excluded).
[Shared libraries]:
ticks total nonlib name
6326 44.2% 0.0% /lib/x86_64-linux-gnu/libm-2.15.so
3258 22.8% 0.0% /.../chrome/src/out/Release/lib/libv8.so
1411 9.9% 0.0% /lib/x86_64-linux-gnu/libpthread-2.15.so
27 0.2% 0.0% /.../chrome/src/out/Release/lib/libwebkit.so
一番上のセクションは、V8が独自のコードよりもOS固有のシステムライブラリ内で多くの時間を費やしていることを示しています。「ボトムアップ」出力セクションを調べて、その原因を探ってみましょう。インデントされた行は「〜によって呼び出された」と読み取ることができます(*
で始まる行は、関数がTurboFanによって最適化されたことを意味します)。
[Bottom up (heavy) profile]:
Note: percentage shows a share of a particular caller in the total
amount of its parent calls.
Callers occupying less than 2.0% are not shown.
ticks parent name
6326 44.2% /lib/x86_64-linux-gnu/libm-2.15.so
6325 100.0% LazyCompile: *exp native math.js:91
6314 99.8% LazyCompile: *calculateMandelbrot https://#:8080/Demo.js:215
**合計時間の44%以上が、システムライブラリ内のexp()
関数の実行に費やされています**!システムライブラリの呼び出しのオーバーヘッドを加えると、全体のおよそ3分の2の時間がMath.exp()
の評価に費やされていることになります。
JavaScriptコードを見ると、exp()
は滑らかなグレースケールパレットを作成するためだけに使用されていることがわかります。滑らかなグレースケールパレットを作成する方法は無数にありますが、本当に指数勾配が必要だとしましょう。ここで、アルゴリズムの最適化が登場します。
exp()
は-4 < x < 0
の範囲の引数で呼び出されるため、その範囲のテイラー近似で安全に置き換えることができます。これにより、乗算と2、3の除算だけで同じ滑らかな勾配が得られます。
exp(x) ≈ 1 / ( 1 - x + x * x / 2) for -4 < x < 0
このようにアルゴリズムを調整することで、最新Canaryと比較してパフォーマンスがさらに30%向上し、Chrome CanaryのシステムライブラリベースのMath.exp()
と比較して5倍向上します。

この例は、V8の内部プロファイラがコードのボトルネックを深く理解するのにどのように役立ち、よりスマートなアルゴリズムがパフォーマンスをさらに向上させることができるかを示しています。
今日の複雑で要求の厳しいWebアプリケーションを表すベンチマークの詳細については、V8が実世界の パフォーマンスを測定する方法をご覧ください。