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

コードキャッシュの改善

· 約7分
Mythri Alle, 主任コードキャッシャー

V8は、頻繁に使用されるスクリプトのために生成されたコードをキャッシュするためにコードキャッシングを使用します。Chrome 66から、トップレベルの実行後にキャッシュを作成することで、より多くのコードをキャッシュしています。これにより、初回読み込み時の解析時間とコンパイル時間が20〜40%削減されます。

背景

V8は、生成されたコードを後で再利用するためにキャッシュする2種類のコードキャッシュを使用しています。1つ目は、個々のV8インスタンス内で利用可能なメモリ内キャッシュです。初回のコンパイル後に生成されたコードがこのキャッシュに格納され、ソース文字列をキーとして利用されます。このキャッシュは同じV8インスタンス内で再利用可能です。もう1つのキャッシュ方法は、生成されたコードをシリアライズしてディスクに保存し、後で利用するものです。このキャッシュは特定のV8インスタンスに限定されておらず、異なるV8インスタンス間で利用できます。このブログ記事では、Chromeで使用されているこの2番目の種類のコードキャッシングに焦点を当てています。(他のエンベッダーもこの種類のコードキャッシングを使用しています。Chromeだけに限定されているわけではありません。ただし、このブログ記事ではChromeでの使用に焦点を当てています。)

Chromeは、シリアライズされた生成コードをディスクキャッシュに格納し、スクリプトリソースのURLをキーとして使用します。スクリプトを読み込む際、Chromeはディスクキャッシュを確認します。スクリプトがすでにキャッシュされている場合、ChromeはシリアライズデータをV8にコンパイル要求の一部として渡します。その後、V8はスクリプトを解析してコンパイルする代わりに、このデータをデシリアライズします。また、コードが引き続き利用可能であることを確認するために追加のチェックも行われます(たとえば、バージョンの不一致によりキャッシュデータが利用できなくなる場合など)。

実データによると、コードキャッシュのヒット率(キャッシュ可能なスクリプト)は高い(約86%)ことが示されています。これらのスクリプトでキャッシュヒット率は高いものの、スクリプトごとにキャッシュされるコード量はそれほど多くありません。分析の結果、キャッシュされるコード量を増やすことで、JavaScriptコードの解析とコンパイルにかかる時間を約40%削減できることがわかりました。

キャッシュされるコード量の増加

従来のアプローチでは、コードキャッシングはスクリプトのコンパイル要求に結びついていました。

エンベッダーは、新しいJavaScriptソースファイルのトップレベルコンパイル中に生成されたコードをV8がシリアライズするよう要求することができました。V8はスクリプトをコンパイルした後にシリアライズされたコードを返しました。Chromeが同じスクリプトを再要求すると、V8はキャッシュからシリアライズされたコードを取得してデシリアライズします。V8は、キャッシュ内に既に存在する関数の再コンパイルを完全に回避します。これらのシナリオは次の図に示されています:

V8は、トップレベルコンパイル中に即座に実行が予想される関数(IIFE)のみをコンパイルし、他の関数は遅延コンパイル用としてマークします。これにより、不要な関数のコンパイルを避けることでページ読み込み時間の改善につながりますが、その一方で、シリアライズデータには即時コンパイルされた関数のコードのみが含まれることになります。

Chrome 59以前では、実行が開始される前にコードキャッシュを生成する必要がありました。以前のV8のベースラインコンパイラ(Full-codegen)は、実行コンテキストに特化したコードを生成していました。Full-codegenは特定の実行コンテキストに対して操作を高速化するためにコードパッチ適用を使用していました。このようなコードは、他の実行コンテキストで使用するためにコンテキスト固有のデータを削除してシリアライズするのが容易ではありませんでした。

Ignitionの導入により、この制限はChrome 59で解消されました。Ignitionは、現在の実行コンテキストで操作を高速化するためにデータ駆動型インラインキャッシュを使用します。コンテキスト依存データはフィードバックベクターに格納され、生成されたコードとは分離されています。これにより、スクリプトの実行後でもコードキャッシュを生成できる可能性が開かれました。スクリプトを実行するにつれて(遅延コンパイル用としてマークされた)関数がさらにコンパイルされていき、より多くのコードをキャッシュできるようになります。

V8は新しいAPI ScriptCompiler::CreateCodeCacheを公開し、コンパイル要求と独立したコードキャッシュのリクエストを可能にしました。コンパイル要求とともにコードキャッシュをリクエストする方法は非推奨となり、V8 v6.6以降では動作しなくなります。バージョン66以降、ChromeはこのAPIを使用してトップレベルの実行後にコードキャッシュをリクエストします。次の図はコードキャッシュをリクエストする新しいシナリオを示しています。コードキャッシュはトップレベル実行後にリクエストされるため、スクリプトの実行中に後でコンパイルされた関数のコードが含まれます。その後の実行(次の図でホットランとして示されています)では、トップレベル実行中に関数をコンパイルする必要がなくなります。

結果

この機能のパフォーマンスは、内部の実世界ベンチマークを使用して測定されました。以下のグラフは、以前のキャッシュ方式と比較した解析およびコンパイル時間の減少を示しています。ほとんどのページで解析およびコンパイル時間の両方が約20〜40%削減されています。

実世界のデータも同様の結果を示しており、デスクトップおよびモバイルの両方でJavaScriptコードのコンパイルに費やされる時間が20〜40%削減されています。Androidでは、この最適化により、ウェブページが対話可能になるまでの時間など、トップレベルのページロードメトリクスが1〜2%削減されます。また、Chromeのメモリとディスク使用量を監視しましたが、目立った退行は見られませんでした。