V8のサンプリングベースのプロファイラの使用

V8には、サンプリングベースのプロファイリング機能が組み込まれています。プロファイリングはデフォルトで無効になっていますが、--profコマンドラインオプションで有効にすることができます。サンプラーは、JavaScriptとC/C++コードの両方のスタックを記録します。

ビルド #

GNを使ったビルドの手順に従って、d8シェルをビルドします。

コマンドライン #

プロファイリングを開始するには、--profオプションを使用します。プロファイリングを行うと、V8はプロファイリングデータを含むv8.logファイルを生成します。

Windows

build\Release\d8 --prof script.js

その他のプラットフォーム (x64ビルドをプロファイリングする場合は、ia32x64に置き換えてください)

out/ia32.release/d8 --prof script.js

生成された出力の処理 #

ログファイルの処理は、d8シェルで実行されるJSスクリプトを使用して行われます。これが機能するためには、d8バイナリ(またはシンボリックリンク、Windowsではd8.exe)がV8チェックアウトのルート、または環境変数D8_PATHで指定されたパスに存在する必要があります。注:このバイナリはログの処理にのみ使用され、実際プロファイリングには使用されないため、バージョンなどは関係ありません。

解析に使用するd8is_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 DevToolsCPUプロファイラは、コードのボトルネックを分析するのに役立ちます。しかし、時には、より深く、より詳細な分析が必要になることがあります。そこで、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が実世界の パフォーマンスを測定する方法をご覧ください。