ブログ 約4分

V8エンジンでパフォーマンスを高める:設計上の選択の研究

Share this article
V8エンジンでパフォーマンスを高める:設計上の選択の研究

V8エンジンは、Googleが開発したC++製のオープンソース高性能JavaScriptエンジンです。Google Chromeに加えて、MongoDB、Node.js、その他多くの人気アプリケーションでも採用されています。

V8がなぜこれほど高速なのか、そしてこのレベルのパフォーマンスを実現するためにどのような技術が使われているのかを探るのは興味深いことです。

OOP、デザインパターン、パフォーマンス

C/C++プログラマーの中には、OOPやデザインパターンを使用するとアプリケーションのパフォーマンスが低下すると誤解している人もいます。V8は、必ずしもそうではないことを示す良い例です。V8は多くのデザインパターンを実装しながら、十分に最適化されています。

使用されているパターンの一部は次のとおりです:

Factory

JavaScriptエンジンがスクリプトを実行すると、検出した変数、関数、配列ごとにインスタンスが作成されます。JSObjectは、これらすべての種類のオブジェクトの親クラスです。

JSObjectを継承するすべてのクラスの一覧は次のとおりです:

v1

V8は必要なオブジェクトを作成するためのファクトリクラスを実装しており、この目的のためにFactory::NewJsObjectメソッドが使用されます。

それを使用しているすべてのメソッドは次のとおりです:

v4

このファクトリはV8のクラスから直接使用されるのではなく、Heapクラスから呼び出されます。これにより、実装に別のレベルの間接化が追加されています。

Visitor

VisitorパターンのWikiページで説明されているとおり:

The visitor design pattern is a way of separating an algorithm from an object structure on which it operates. A practical result of this separation is the ability to add new operations to existing object structures without modifying those structures. It is one way to follow the open/closed principle.

Factoryパターンと同様に、このパターンも実装にある程度の間接化を追加しますが、コードの可読性と保守性を高めます。

V8のソースコードには、Visitorパターンを実装するクラスが多数含まれています。

v2

V8の開発者は実行パフォーマンスを最適化する必要があるにもかかわらず、コードにある程度の間接化を進んで導入しています。確かに、デザインパターンや一部のC++機構を使用すると、実装によって追加される間接化のためにパフォーマンスへ影響する可能性があります。しかし、それはどちらかと言えばマイクロ最適化の問題です。大幅なマクロレベルの最適化は、アプリケーションのニーズに合わせた設計上の選択により大きく依存します。

実行を最適化するためのV8の設計上の選択

1. 隠しクラスと高速なプロパティアクセス

JavaScriptは動的なプログラミング言語です。プロパティは実行中にオブジェクトへ追加したり、オブジェクトから削除したりできます。つまり、オブジェクトのプロパティは時間とともに変化する可能性があります。

前述のとおり、JSObjectはJavaScript関数を表すJSFunctionや、JavaScript値を表すJSValueの親クラスです。しかし、FunctionやValueのようなクラスを表すJSObjectの派生クラスは存在しません。ほとんどのJavaScriptエンジンは、オブジェクトのプロパティを格納するために辞書のようなデータ構造を使用します。そのため、プロパティへアクセスするたびに、メモリ内のプロパティ位置を解決する動的ルックアップが必要になります。

このアプローチでは、JavaScriptのプロパティアクセスは、JavaやSmalltalkなどのプログラミング言語におけるインスタンス変数へのアクセスよりも、通常はるかに低速になります。これらの言語では、オブジェクトのクラスによって定義される固定のオブジェクトレイアウトがあるため、インスタンス変数はコンパイラが決定する固定オフセットに配置されます。アクセスは単純なメモリのロードまたはストアであり、多くの場合1命令だけで済みます。

V8は、JavaScriptプロパティへのアクセスに必要な時間を短縮するために、隠しクラスという概念を使用します。V8はプロパティへのアクセスに動的ルックアップを使用しません。その代わり、V8は内部で隠しクラスを動的に作成します。

2. 動的なマシンコード生成

V8は、JavaScriptソースコードが最初に実行されるときに、それを直接マシンコードへコンパイルします。中間バイトコードもインタプリタもありません。プロパティアクセスはインラインキャッシュコードによって処理され、V8の実行中に他のマシン命令へパッチ適用される場合があります。

3. 効率的なガベージコレクタ

V8は、ガベージコレクションと呼ばれるプロセスを通じて、不要になったオブジェクトが使用していたメモリを回収します。高速なオブジェクト割り当て、短いガベージコレクションの停止時間、メモリ断片化の防止を実現するために、V8はストップ・ザ・ワールド型で世代別、かつ正確なガベージコレクタを採用しています。つまり、V8は次のように動作します:

  • ガベージコレクションサイクルを実行するときにプログラムの実行を停止する。
  • ほとんどのガベージコレクションサイクルでオブジェクトヒープの一部だけを処理し、アプリケーション停止の影響を最小限に抑える。
  • メモリ内のすべてのオブジェクトとポインタの位置を常に正確に把握し、オブジェクトを誤ってポインタとして識別することを防ぐ。こうした誤認はメモリリークにつながる可能性がある。

結論

パフォーマンスを最優先するという理由だけで、OOPやデザインパターンを使用しないという選択は、良くない考えかもしれません。数マイクロ秒を得られるかもしれませんが、コードの可読性と保守性を失う可能性があります。

大幅なマクロレベルの最適化は、アプリケーションのニーズに合わせた設計上の選択により大きく依存します。

Share this article