メインコンテンツまでスキップ

V8 リリース v7.9

· 約6分
Santiago Aboy Solanes, ポインター圧縮のエキスパート

6週間ごとに、リリースプロセスの一環として新しい V8 のブランチを作成します。各バージョンは、Chrome のベータマイルストーンの直前に V8 の Git マスターからブランチ分岐されます。本日、V8 バージョン 7.9 を発表できることを嬉しく思います。このバージョンは数週間後の Chrome 79 ステーブルと連携してリリースされるまで、ベータ版として利用可能です。V8 v7.9 には開発者向けの様々な新機能が満載されています。本投稿では、リリースを控えた注目のポイントをいくつかご紹介します。

パフォーマンス(サイズ & スピード)

Double ⇒ Tagged への遷移の非推奨を削除

以前のブログ投稿でご紹介したように、V8 はオブジェクトのシェイプにおけるフィールドの表現方法を追跡します。フィールドの表現が変更されると、現在のオブジェクトのシェイプが「非推奨」となり、新しいフィールド表現を持つ新しいシェイプが作成されます。

これには例外があり、古いフィールドの値が新しい表現と互換性がある場合には、オブジェクトシェイプ内で新しい表現に置き換えるだけで使用できます。V8 v7.6 では、Smi ⇒ Tagged や HeapObject ⇒ Tagged の遷移に対してインプレースでの表現変更が可能になりましたが、MutableHeapNumber 最適化のために Double ⇒ Tagged を回避することはできませんでした。

V8 v7.9 では MutableHeapNumber を削除し、HeapNumbers を Double 表現フィールドに属するときに暗黙的にミュータブルとして扱うようにしました。これにより、HeapNumbers の取り扱いが少し注意深くなる必要がありますが、HeapNumbers は Tagged 表現と互換性があるため、Double ⇒ Tagged の場合でも非推奨を回避できます。

この比較的単純な変更により、Speedometer AngularJS スコアが 4% 向上しました。

Speedometer AngularJS スコア向上

組み込みでの API ゲッターの処理

これまで、V8 は埋め込み API(例えば Blink)によって定義されたゲッター(Node.nodeTypeNode.nodeName など HTML スペックで定義されるもの)を処理する際、C++ ランタイムを常に呼び出していました。

V8 はゲッターを読み込むために組み込みでプロトタイプを全て巡回し、ゲッターが API で定義されていることを確認した後、ランタイムにフォールバックしていました。C++ ランタイムでは、ゲッターを実行する前にプロトタイプチェーンを再度巡回してゲッターを取得していました。このように多大な重複作業が発生していました。

インラインキャッシュ (IC) メカニズム により、この問題を軽減できます。V8 は初回の C++ ランタイムでのミス後に IC ハンドラをインストールします。しかし、新しい 遅延フィードバック割り当て により、関数がしばらく実行されるまで IC ハンドラがインストールされない場合があります。

V8 v7.9 では、これらのゲッターを C++ ランタイムにフォールバックすることなく組み込み内で処理できるようになりました。IC ハンドラがインストールされていない場合でも、API ゲッターに直接アクセスできる特別な API スタブを利用することでこれを実現しています。これにより、Speedometer の Backbone および jQuery ベンチマークで IC ランタイムに費やされる時間が 12% 減少しました。

Speedometer Backbone および jQuery の向上

OSR キャッシング

特定の関数がホットであると判断した場合、V8 は次回の呼び出し時に最適化するようにマークします。その後、関数が再び実行されると、V8 は最適化コンパイラを使用して関数をコンパイルし、次回以降の呼び出しから最適化されたコードを使用します。しかし、長時間実行されるループを持つ関数ではこれだけでは十分ではありません。

V8 はスタック上置換 (OSR: On-Stack Replacement) と呼ばれる技術を使用して、現在実行中の関数に最適化されたコードをインストールします。これにより、ホットループに入った最初の関数実行の間に最適化されたコードを使用することができます。

関数が2回目に実行される際に、再び OSR が発生する可能性が非常に高いです。V8 v7.9 以前では、OSR を実現するために関数を再最適化する必要がありました。しかし、v7.9 からは、OSR キャッシングを追加して、OSR 置換のために使用された最適化コードをキャッシュするようになりました。キャッシュは OSR 関数でエントリポイントとして使用されたループヘッダーをキーとして扱います。これにより、一部のピークパフォーマンスベンチマークで 5–18% のパフォーマンス改善が実現しました。

OSR キャッシングの向上

複数のコードスペースのサポート

これまで、各WebAssemblyモジュールは64ビットアーキテクチャ上で正確に1つのコードスペースで構成されており、それはモジュール作成時に予約されていました。これにより、モジュール内で近距離コールを使用することが可能でしたが、arm64では128 MBのコードスペースに制限され、x64では1 GBを事前に予約する必要がありました。

v7.9では、V8が64ビットアーキテクチャでの複数のコードスペースをサポートするようになりました。これにより、必要と推定されるコードスペースだけを予約し、必要に応じて後でコードスペースを追加することが可能になります。コードスペース間の距離が近距離コールを超える場合には、遠距離ジャンプが使用されます。その結果として、1プロセスあたり約1000のWebAssemblyモジュールに制限されていたV8が、実際に利用可能なメモリ量だけが制限となり、数百万のモジュールをサポートできるようになりました。

V8 API

APIの変更点については、git log branch-heads/7.8..branch-heads/7.9 include/v8.h を使用してください。

アクティブなV8チェックアウトを持つ開発者は、git checkout -b 7.9 -t branch-heads/7.9コマンドを使用してV8 v7.9の新機能を試すことができます。または、Chromeのベータチャンネルに登録して、すぐに新機能を試してみることも可能です。