Lars Bak と Jay Conrod がいくつかのビデオと論文で説明されているように、V8の非表示クラスの主なアイデアは、具体的なオフセットを格納するか、おそらくこれを含む別の非表示クラスへの移行ですすべてのプロパティを含むハッシュテーブルで「遅い」検索を行う代わりに、すべてのオブジェクトのプロパティのオフセット。
たとえばpoint.x
を取得するなど、昔ながらのプロパティアクセスの方法では、x
からハッシュコードを計算し、配列を調べる必要があります(プロパティが追加されたときにハッシュコードの衝突がなかった場合)すぐに値を取得します)。しかし、新しい方法では、非表示のクラス(Map
?)があり、それを反復してx
キーが含まれているかどうかを確認する必要があります。 x
フィールドのオフセットによるプロパティ値。または、より複雑なケースでは、別の隠しクラスへの遷移を通じていくつかの追加の手順を実行する必要があります。したがって、すべてのプロパティをハッシュテーブルに保存することと、隠しクラスを使用することの違いはわかりません。たぶん、わからないアセンブリトリックがあるのでしょうか。
同様の手法を使用するV8およびその他の言語の実装は、ジャストインタイムコンパイラです。それらはコードを生成し、その生成されたコードは投機的に最適化されます(特殊化が無効であることが判明した場合、より低速でより一般的なコードにフォールバックするためのチェックが行われます)。したがって、コードを生成するとき、JITコンパイラーはオブジェクトの非表示クラスが何であるかをよく推測します-型推論から、またはプログラムの実行中に収集されたデータから-そして、次のようなコードを生成します。
compare hidden class pointer to address of assumed
if not equal, de-optimize, otherwise:
access the property at a hard-coded offset
上記の各行は、x86上の単一のアセンブラー命令にすることができます。このコードを生成するときに非表示のクラスを想定しているため、JITコンパイラは非表示のクラスからオフセットを取得できますJITコンパイル時にで、コードにハードコード化できるため、実行されるコードが非表示のクラスに触れます。
重要なのは、これが特殊なコードを生成するJITで使用されていることです。
次のような関数があるとします。
function add_points(a, b) {
return new Point(a.x + b.x, a.y + b.y);
}
V8は実際には、渡される可能性のあるさまざまな引数に対して、この関数のさまざまなバージョンを生成します。たとえば、aとbが両方とも非表示のPointクラスのメンバーである場合にのみ使用される関数のバージョンを生成します。
Point add_points(Point a, Point b) {
return new Point(a.x + b.x, a.y + b.y);
}
そのトリックは、この関数は実際には隠しポイントクラスのオブジェクトにのみ使用されることがわかっているため、xとyは常に同じオフセットになるということです。そのため、毎回それを検索する必要はありません。コンパイラーは、この関数の作成中に一度検索して、生成されたコードにオフセットを挿入しました。