Mozilla Hacksの「A crash course in just-in-time (JIT) compilers」を読みました。2017年の記事ですが、JavaScriptエンジンがどのように実行中のコードを観察し、必要な部分だけをコンパイルしていくのかを、WebAssembly理解の前提として整理してくれる内容です。
この記事は、Lin Clark氏によるWebAssembly解説シリーズの第2回です。前回の「A cartoon intro to WebAssembly」では、WebAssemblyがJavaScriptを置き換えるものではなく、同じWebアプリの中で併用される低レベル実行形式として説明されていました。今回のJIT記事は、その比較対象であるJavaScript実行系の複雑さを見せてくれます。
記事の要点
- インタプリタはすぐに実行を始められる一方、ループなどでは同じ翻訳作業を繰り返すため効率が落ちます。
- コンパイラは先に機械が理解しやすい形へ変換するため、実行時は速くなりますが、開始前の準備時間が必要です。
- JITはその中間にあり、実行しながらコードの利用状況を監視し、よく使われる箇所をコンパイル対象にします。
- 「warm」な関数にはbaseline compilerが比較的軽いコンパイルを行い、「hot」な関数にはoptimizing compilerがより強い最適化をかけます。
- ただしJavaScriptは動的型付けなので、最適化は実行時に観察した型やオブジェクト形状への仮定に依存します。仮定が崩れるとdeoptimizationが起き、最適化済みコードを捨てて戻ることがあります。
リンク先も見てわかったこと
記事内から参照されている次の記事「A crash course in assembly」では、ALU、レジスタ、RAM、命令、opcode、そして中間表現(IR)という流れで、コンパイラが最終的にどのような機械寄りの表現へ落としていくのかが説明されています。JIT記事だけを読むと「実行中に速くする仕組み」という印象で終わりがちですが、assembly側まで見ると、JITが単なる魔法ではなく、限られた時間の中でどこまで機械向けに近づけるかを判断している仕組みだとわかります。
また、記事中では翻訳の比喩として映画Arrivalも参照されています。この比喩はかなり効いていて、人間向けに書かれた高水準コードを、機械が扱いやすい形へどう翻訳するかという問題の難しさを直感的に捉えやすくしています。
気づき
今回の気づきは、JITコンパイラは「全部をコンパイルして速くする装置」ではなく、「どこにコンパイル時間とメモリを使うべきかを実行中に判断する予算配分の仕組み」だという点です。速くするためには監視も必要ですし、型の仮定も必要ですし、仮定が外れた時に戻る道も必要です。つまりJavaScriptの高速化は、単純な変換ではなく、観察、推測、最適化、撤回の連続として成り立っています。
この観点で読むと、WebAssemblyが目指した「より予測しやすい性能」の意味も見えやすくなります。JavaScript JITが実行時の推測に多くを背負うのに対し、WebAssemblyはあらかじめ型や構造が整理された形で渡せるため、エンジン側の迷いを減らせる。JITの記事は、WebAssemblyの利点を理解するための背景説明として今読んでも価値があります。
読むとよさそうな人
- JavaScriptがなぜ昔より速くなったのかを知りたい人
- JIT、baseline compiler、optimizing compiler、deoptimizationの関係をつかみたい人
- WebAssemblyの速さを、単なる「ネイティブに近いから」ではなくJavaScript実行系との比較で理解したい人

コメントを残す