V8はどうやってJavaScriptコードを最適化しているのか?

  • このエントリーをはてなブックマークに追加

   

僕の過去記事で、NodeJSがなぜ速いかについて話した。今日は、V8について話したいと思う。

1-eiwHsFOQbAWWcifvC_zG7w

多分、これを読んでいる人の中には、JavaScriptの実行はC++と同じくらい速いと聞いたことがある人もいるかもしれない。そもそも、どうしてそれが可能になるのか理解できない人もいるだろう。C++がAhead-of-Time (AOT) コンパイルの静的型付け言語なのに対し、JavaScriptはJust-In-Time(JIT)コンパイラを搭載した動的型付け言語だ。そしてどういうわけか、最適化されたJavaScriptコードの実行速度はC++より若干遅いか、同じ速さですらある。

これがなぜなのかを理解するには、V8実装の基礎を知る必要がある。これは巨大なテーマなので、この記事ではV8の主な特徴を説明するだけに留めようと思う。隠しクラス、SSA、ICなどもっと詳しく知りたかったら…、僕の次の記事で紹介するつもりだ。

AST

全ては、JavaScriptコードとその抽象構文木(AST)から始まる。

コンピュータサイエンスにおける抽象構文木(AST)または構文木とは、プログラミング言語で書かれたソースコードの抽象統語構造の木表現である。その構文は「抽象的」であり、実際の構文に出てくる情報を細部まで表示しない。

コンピュータサイエンスにおける抽象構文木(AST)または構文木とは、プログラミング言語で書かれたソースコードの抽象統語構造の木表現である。その構文は「抽象的」であり、実際の構文に出てくる情報を細部まで表示しない。

Full-Codegenコンパイラ

このコンパイラの主な目的は、最適化なしでJavaScriptコードをネイティブコードにできる限り速くコンパイルすることだ。これはどんなケースでも対処してくれて、JavaScript関数の様々な場所におけるデータ型情報を集めた型フィードバック・コードを含んでいる。

ASTを伝ってノードを歩き、マクロアセンブラに直接命令を出す。このオペレーションの結果が一般的なネイティブコードだ。

以上。特別なことは何もない。ここでは最適化は行われず、複雑なケースのコードは実行時プロシージャへの命令により対処され、全てのローカル変数はヒープに記憶される…等々。

一番面白いのは、関数が「ホットに」なり最適化する時が来たとV8がみなした時だ。そうなると、Crankshaftコンパイラの出番だ。

Crankshaftコンパイラ

前述したとおり、full-codegenコンパイラは、関数の型フィードバック情報を集めてくれるコードとともに一般的なネイティブコードを作り出す。関数が「ホットに」なる時(ホットな関数とは、頻繁に呼び出される関数のこと)、CrankshaftはASTとその情報を使って、関数のための最適化コードをコンパイルすることができる。その後、最適化された関数はon-stack replacement (OSR)を用いて、最適化されていないものと置き替える。

しかし…最適化された関数は、全てのケースをカバーしているわけではない。もし型に不具合が起こったら、例えば、関数が整数の代わりに浮動小数点を返し始めたら、最適化された関数は脱最適化され、前の非最適化コードに置き換えられる。そんなの嫌ですよね?

例えば、2つの数字を加える関数があったとする。


const add = (a, b) => a + b;
// Let's say we have a lot of calls like this
add(5, 2);
// ...
add(10, 20);

この関数を整数のみで何度も呼び出すと、型フィードバック情報は、このaやbの引数は整数であるという情報になる。この情報とこの関数を使って、Crankshaftはこの関数を最適化できる。しかし、例えば下のような指示をしてしまうと、全て壊れてしまう。


add(2.5, 1); // float number as the first argument

Crankshaftは前の型フィードバック情報を踏まえ、この関数を通るのは整数のみだと想定しているのに、浮動小数点を渡そうとしている。このケースに対処する最適化コードはないため、単に最適化されていない状態に戻される。

あなたは、Crankshaftで一体どんなマジックが行われているんだ?と聞きたいかもしれない。ええと、Crankshaftコンパイラでは、連携する部分がいくつかある。

  • 型フィードバック(上述した通り)
  • Hydrogenコンパイラ
  • Lithiumコンパイラ

Hydrogenコンパイラ

Hydrogenコンパイラは、ASTと型フィードバック情報を入力データとして受け取る。この情報に基づき、Hydrogenは、静的単一代入形式(SSA)での制御フローグラフ(CFG)からなる高水準の中間表現(HIR)を生成する。

HIRを生成している間、定数畳み込みやメソッドのインライン化など、いくつかの最適化が適用される(…V8最適化のトリックについては次回の記事で話すつもりだ)。

これらの最適化の結果は、最適化された制御フローグラフ(CFG)であり、次のコンパイラ(実際のコードを生成するLithiumコンパイラ)への入力データとして利用される。

Lithiumコンパイラ

Lithiumコンパイラは最適化されたHIRを受け、特定マシンの低水準の中間表現(LIR)に翻訳する。LIRは、概念上マシン語に似ているが、それでも大部分はプラットフォーム独立型だ。

LIR生成の間もなお、Crankshaftはいくつかの低水準最適化を適用することができる。LIRが生成されると、Crankshaftは各Lithium命令に向けた一連のネイティブ命令を生成する。

その後、結果として生成されたネイティブ命令が実行される。

まとめ

これが、JavaScriptを最適化し、C++と同じくらい速く実行させるためにV8の内部で起こるマジックだ。それでも、下手に書かれたJavaScriptは最適化されず、最適化のメリットも得られないということは理解しておいてほしい。

次の記事で、V8最適化のトリック、コードのボトルネックを紐解く方法、脱最適化されたものの探し方、制御フローグラフ(CFG)の調査について話す予定だ。

原文:https://blog.ghaiklor.com/how-v8-optimises-javascript-code-a0f3bbd46ac9#(2017-1-18)
※元記事の筆者には直接翻訳の許可を頂いて、翻訳・公開しております。

 -Tech

FAworksではプロのコンサルタントが案件をお探しします

  関連記事

これから必ず伸びる!最低限抑えておきたい技術トレンド3つ(2015年度版)

※この記事は2015年度版です。最新の技術トレンド情報は下記の記事をご覧ください。 『「AIって何?

GolangをJavaと比べてみた~Java愛好家がGoの機能を見たときの第一印象~

GolangをJavaと比べてみた~Java愛好家がGoの機能を見たときの第一印象~

最初に断っておきたいのだが、私はGoのエキスパートではない。2~3週間前にGoを勉強し始めたばかりな

React/Fluxにおける問題とReducerが切り開く道

私がReact/Fluxアプリケーションを書いてきて、もう1年になる。Flux開発の1年を振り返って

【比較表あり】非エンジニアの人にも知ってほしい。エンジニアに優しいチャット・コミュニケーションツールまとめ

エンジニアに合ったコミュニケーションツール プロジェクトを円滑に進行させるためにも、チームでのコミュ

エンジニアの作業効率を一気に上げてくれる、無料Google Chrome拡張機能おすすめ20選

ちょっとした時短の積み重ねが作業時間を減らしてくれる 情報収集やブラウザチェック、ルーティーンワーク

PHPエンジニアのキャリアパスと年収の遷移モデル

PHPはWeb開発言語の中でも、1995年から人気が衰えない言語です。主に動的なWebページ制作等に

1家に1人!旦那がエンジニアだと便利な5つのこと

よく便利なものに対して「1家に1台」とか言いますよね。 まさしくエンジニアはその「1家に1人」の便利

「AIって何?」なんて今さら聞けない!最低限抑えておきたいこれからの技術トレンド4つ(最新版)

【関連記事】 ❏これから必ず伸びる!最低限抑えておきたい技術トレンド3つ(2015年度版) ❏海外エ

webを利用してイケてるガールにデプロイする方法

webを利用してイケてるガールにデプロイする方法

エンジニアに出会いはない。 彼らが業務で関わりを持つのは、チームのメンバーとPCのみ。 そしてそのメ

LL系カンファレンスの歴史

WEBエンジニアの祭典!LL系カンファレンスの歴史

例年夏から秋にかけて開催されているLL(lightweight language, 軽量プログラミン