クイック エンジニアリングブログ

株式会社クイック Web事業企画開発本部のエンジニアリングチームが運営する技術ブログです。

【図解】JavaScriptのHidden Classのまとめ

初めまして!エンジニアの🍣🍶です。

ユーザの体験を悪くしないために待ち時間を短くすることも重要ですよね!
なのでJavaScript(以下JS)が実行速度をあげている仕組みの一つであるHidden Classを簡単にまとめてみました!

Hidden Classが必要な理由は??

Javaなどの静的型付き言語では、オブジェクト構造は定義された後は変更できません。
なのでコンパイル時にオブジェクトのレイアウトを決定できます。
そのため、オブジェクトのプロパティ値は一度の参照で取得できます。

class Point {
  int x;
  int y;
  double z;
}
Pointのインスタンスだよー
x - 4byte
y - 4byte
z - 8byte

しかしJSは動的型付け言語です。
なので変数の型は実行時に決まりますし、オブジェクトにプロパティを追加したり削除したりも簡単にできます。
そのせいで、エンジンはオブジェクト内のプロパティの値のメモリアドレスを決定するのが難しいです。

考えられる手法としてはhash tableがあります。
とてもザックリ説明するとkeyをhash化してそれを添字に値を格納する配列です。 その弱点として毎回hash関数を実行することになります。

大部分がobjectで構成されるJSではこれは大きな問題です。

なのでHidden Classと呼ばれる物を使ってより高速にアクセスできる手法を取っています!
(ここではHidden Classと呼びますがSpiderMonkeyではShape、V8ではMapと呼ばれてたりします。)

全てのJSObjectはHidden classへの参照を持っていて、 Hidden Classはオブジェクトのプロパティのオフセットを保持しています。
そのためHidden Classによってそのプロパティがどこにあるのかわかるのです!

ここからはHidden ClassがどのようにJSObjectの変化とともに動くのか簡単に見ていきます!
(いろいろ省略して簡素化しています)

JSObjectの変化とHidden Classの関係

class Point {
    constructor(x) {
        this.x = x;
    }
}

const p1 = new Point(1);
`p1`に対してHidden Class1が作られました!
f:id:aimstogeek:20191121105011p:plain
p1.y = 2;
`p1`にプロパティ`y`を追加する
  1. Hidden Class1に参照しているObjectに`y`が追加された時にHidden Class2を参照するというメモが追加される
  2. `p1`が参照するのがHidden Class2に切り替わる
f:id:aimstogeek:20191121105016p:plain
p1.z = 3;
一個上と同じようなことが起きます!
f:id:aimstogeek:20191121105111p:plain
const p2 = new Point(2);
ここで新しいObjectを作ってみます。 `p1`を作成した時に作られたHidden Classを参照します!
f:id:aimstogeek:20191121105121p:plain
p2.z = 5;
今度はここで`y`より先に`z`を作ってみます! すると新しいHidden Classが作られました!
f:id:aimstogeek:20191121105133p:plain
p2.y = 7;
ここで`p2`に`y`を追加して`p1`と同じ形にしてみました。 ですがここでも新しいHidden Classが作られてしまいました。。。。
f:id:aimstogeek:20191121105141p:plain

これでプロパティを同じ順番で作ることの重要さが分かりましたね!

const p3 = new Point(3);
p3.z = 1;
p3.y = 1.1;
f:id:aimstogeek:20191121105149p:plain

今度はp3を作りp2と同じ順番でプロパティを追加しました!

ですが同じHidden Classを参照していません!

これは内部的な型の不一致が原因です。

p2.yには7を代入しました、これはSmi(Small integer)として扱われます、

しかしp3.yには1.1を代入しました、これはDouble型なのでSmiとして扱うことができません。。。

そのため新しいHidden Classが作られてしまうのです。

今回はプロパティを追加する順番は一緒だったので単に新しいHidden Classが作られたわけではなく

Hidden Class5にこれを使うのやめてHidden Class6を使ってねーと言うメモが追加されます。 なのでこの後に

p2.x = 1;

こういったコードを実行するとp2がHidden Class6を参照するようになります!

Hidden Classが一致しないとどのような問題があるの?

単純にHidden Class分のObjectが無駄に出来ていくという点があります。 大したことなさそうですね!

問題はインラインキャッシュやJITの前提になっていることです。

const f = (point) => point.x + point.y + point.z;

こんな関数があったとします。 これが実行された時JIT(Just In Time Compiler)という機能が動きます。 これは何をするかというと型を基準に、より早いコードに内部的に変換してくれる機能です。

f(p1);

ここでこの関数はp1の Hidden Classの変数が渡されるんだ! と解釈して、それを元により速く動くように変換します。

f(p2);

しかしこのように違うHidden Classの変数を渡してしまうと あれ?なんか違うのが来た!とより速く動くように変換した物を元に戻してしまいます。

まとめ

JSのObjectは基本的にはhash tableじゃないよ!

ちょっとだけJSの気持ちを考えて プロパティの宣言の順序を気にしたり、 内部の型を意識したらちょっと速くなるかも!!

しかも型を意識することはコードの読みやすさにもつながるので一石二鳥!

TypeScriptはいいぞー


\\『真のユーザーファーストでマーケットを創造する』仲間を募集中です!! //

919.jp