はじめに
V8 エンジンは、Google のオープンソースで Javascript, WebAssembly のエンジンです。
V8 は、 C++ で書かれており、Google Chrome や Node.js で動いています。
Javascript はどうやって動いているの?
V8 エンジンは、2つのコンポーネントで構成されています。
- Meomry Heap(メモリの割り当てなど)
- Call Stack(コードを実行するためのスタック)
Javascript から使える API (setTimeout
など)には様々なものがありますが、これらは V8 エンジンから提供しているものではなく、ブラウザによって、提供されているものです。
Call Stack
Javascript は、シングルスレッドで動くので、Call Stack もシリアルに動きます。
例えば、このようなコードがあるとします。
function multiply(x, y) {
return x * y;
}
function printSquare(x) {
var s = multiply(x, x);
console.log(s);
}
printSquare(5);
すると、以下のようにスタックされます。
エラーが throw される場合もこの CallStack が使われているので、エラーを辿ることができます。
function foo() {
throw new Error('SessionStack will help you resolve crashes :)');
}
function bar() {
foo();
}
function start() {
bar();
}
start();
また、無限ループの処理をする際は、このようになります。
function foo() {
foo();
}
foo();
このエラーはシングルスレッドなので見つかりやすいですが、マルチスレッドになると少し複雑になります。しかし、その分シングルスレッドでの処理は遅くなってしまいます。
並行性とイベントループ
例えば、画像を変換する処理など時間がかかる処理をするプログラムがあった場合になかなか表示されないページを見たことがあるかもしれません。
その時はダイアログで警告されます。
こういったサイトは、UX 的にとても残念なページですね。
これを解消するために asynchronous callbacks
を使った方法があります。
TurboFan コンパイラ, Ignition インタプリタ
Javascript は、 JIT (just-in-time compile) で動作しているので、いくつかのトレードオフが存在します。
- コンパイルして生成されたネイティブコードは、実行されるピーク時の速度が早い一方で、コードの量が多いほど、最初に実行されるまでに遅延が発生する。一方で、トランスレーターを使った場合は、実行されるまでの遅延は小さいけど、実行時の速度が遅い。
- JavaScriptエンジンは、パフォーマンスを良くしようとするとメモリの消費量が多くなる。一方で、省メモリを進めようとするとパフォーマンスは悪化する。
これらを最適化するため、V8 では様々な改善が行われてきました。V8 のバージョン 5.9 までは2つのコンパイラが使われていましたが、現在では、TurboFan コンパイラ, Ignition インタプリタが使われています。
Ignition インタプリタ
中間コードを生成して実行するインタプリタ。メモリ消費が小さく動く、Fast Startupも最適という特徴があります。
- モバイルなどのメモリの小さな環境で動かすのに最適
- Startup Timeに最適化すべきコードの実行に向いている
- TurboFanと組み合わせることで、実行速度を最適化できる
TurboFan コンパイラ
主として、最適化を行うことを目的としたコンパイラです。
使われ方としては、拡張的な感じになります。ただ、あらゆる新しいJavaScript機能に対応していき、その場合も活用されます。
WebAssembly のバックエンドでもあります。
最近はtry/catch/finallyとか、ES2015+の最適化が可能になりました。
おわりに
V8 のコンパイラについては、いろいろと最適化が繰り返され、今のシンプルな形になっています。こうすることで管理しやすくしているのか思います。Web エンジニアとしては、V8 の仕組みはしっかり知っておきたいですが、まだまだわからないところも多いです。また、ガベージコレクションまでは書ききれませんでしたので、その辺はまた別の機会に書いてみたいと思います。
参考