[翻訳]JavaScriptエンジンにおける遅延解析

5529 ワード

原文:http://ariya.ofilabs.com/2012/07/lazy-parsing-in-javascript-engines.html
現代のJavaScriptエンジンは、関数体の解析操作を本当に必要な時に遅らせて行うことができます.以下では、なぜこのようにしてエンジンが具体的にどのように実現されるのかを説明します.
IEチームは最近のブログ「IE 10とWindows 8におけるJavaScript性能の発展」において、遅延解析を使用してIE 10の性能を向上させると言及しています.実際には、IE 9の正式版では、このような最適化手段が実現されましたが、IE 10だけはさらに改善されました.
コマンドの最初の実行時間をさらに下げるために、Chakraはその関数が実行される直前にのみ解析してバイトコードを生成する仕組みを遅延解析といいます.
簡単な例を通して、遅延解析とは何かを見てみましょう.
function add(x, y) { return x + y; }
function mul(x, y) { return x * y; }
alert(add(
40, 2));
エンジンがこのコードを実行する前に、まずコードをその解像度に送ります.解析器は入ってきたコードを文法的に分析して、最後に抽象的な文法ツリーというものを生成します.文法ツリーの具体的な長さを見たいなら、このEsprimaに基づいてみてもいいです.例のコードは短いですが、文法ツリーはまだ複雑ですので、人間言語で解析結果を説明します.
一つの関数addを宣言します.xとyの二つのパラメータを受け入れます.一つのreturn文だけを含みます.戻り値はxとyの和です.一つの関数mullを宣言します.xとyのパラメータを受け入れます.一つのreturn文だけを含みます.戻り値はxとyの積です.関数alertを呼び出します.パラメータは関数addの戻り値です.パラメータは40と2です.
このシンタックスツリーがあれば、次の一連の操作ができます.最後のディストリビュータでコードを実行し、ポップアップボックスに結果が表示されます.上のいくつかのステップの解析動作では、関数mullの解析は完全に無駄です.最終的にはalert関数はadd関数のみを呼び出しています.mull関数はまったく使用されていません.この簡単なデモ例以外は、実際には、実際のWebでも、多くのステートメントされた関数が一度も呼び出されていませんでした.
しかし、現代のJavaScriptエンジンは、すべてのコードを一度に解析するのではなく、遅延解析(lazy parsing)を使っています.同じコードでは、解析結果は次のようになります.
関数addを宣言します.関数体は「{return x+y]」.関数mullを宣言します.関数体は「{return x*y]」.呼び出し関数alert.パラメータは呼び出し関数addの戻り値です.パラメータは40と2.
つまり、解析器は各関数の関数体のコードを解析していません.関数全体を保存するだけで、この関数が本当に実行したい時に解析を行います.
関数addを呼び出します.解析されていないことが分かりました.そこで、解析を開始しました.「{return x+y]」の解析結果は、xとyの二つのパラメータを受け入れます.これはreturn文だけを含みます.戻り値はxとyの和です.
大体において、その関数のソースコードを解析する任務が遅延されました.必要な時には、その関数(add関数)がすぐ実行される時にのみ解析されます.しかし、この時には、この関数のソースコードを解析する必要がありますが、今回の解析と正常解析の違いは、今回の解析過程を通じてのみ簡略化されました.関数全体の関数体、つまりfunction add(x, y) {と関数体の末尾の}との間のコードを正確に見つけることができます.このタスクは正規表現または他の任意の形式のスキャンによっては実現できません.このような不確実性は、簡略化されているので、関数の末尾の大かっこを早く見つけて、他の独立した動作をする必要がありません.これは、いくつかの正常な解析の過程で必要な操作を省略することができるという意味です.まず一つは、文法ツリーを生成する必要がないことです.この時、この文法ツリーが必要とされる人がいないからです.また、コードパスのためにメモリ空間を割り当てなくてもいいです.システムリソースを割り当てて、この操作がエンジンの運行速度を上げることができます.
次の例を挙げます.例えば、いい文章を偶然発見しました.でも、すぐに読まないことにしました.これからは本当にこの知識を知る必要がある時に見に来ます.文章の本文をメモソフトに保存してください.この文章を素早く読んでください.本文の始めと終わりは早いです.本文の始まりと終わりを見つけたら、これらの文字を選んで、クリップボードにコピーして、ノートソフトのウィンドウに切り替えて、最後に中に貼り付けます.何日間後に、本当にその文章の内容を使う必要がある時になります.そのノートを開けて、きちんと読んでください.
一つのwhile文を解析して、正常な解像度と遅延解像度の違いを見てみましょう.あなたはすでに知っています.while文の文法は以下の通りです.
'while'('Expression')'Sttement
これらのコードを解析して、コード構造を表す抽象的な文法ツリーを生成する必要があります.解析器でwhile文を解析するコードをJavaScriptで実現すると(エンジンの中でC++)、そうなります.
function realParseWhileStatement()
{
expect(
'while');
expect(
'(');
var expression = parseExpression();
expect(
')');
var statement = parseStatement();

// AST return {
type:
'WhileStatement',
test: expression,
body: statement
};
}
遅延解析であれば、戻りの結果を保存する必要がなくなります.コードは簡単になります.
function lazyParseWhileStatement()
{
expect(
'while');
expect(
'(');
parseExpression();
expect(
')');
parseStatement();
}
明らかに、他のいくつかの関数があります.様々な文法構造を解析します.
遅延解析中にネストされた関数宣言があったらどうすればいいですか?同じルールでも関数は遅延解析されます.関数の中の夢泥棒空間はありますか?
実際には、実際の遅延解析器はより複雑になります.厳格なモードと解析エラーを適切に処理するだけでなく、スタックオーバーフローなど他の細かい問題も避けなければなりません.
次に、二つの主流のJavaScriptエンジンに遅延がどのように実現されるかを見てみましょう.
まずJavaScripptCore(JSC)を見てください.SafariのWebkitレンダリングエンジンに内蔵されています.JSCのソースコードはWebkitソースのSource/JavaScriptCoreディレクトリに保存されています.遅延負荷に関するソースファイルは以下の通りです.
parser/Parser.h
parser/Parser.cpp
parser/SyntaxChecker.h
JSCでは、通常の解像度と遅延解析器は本質的に同じコードを使用しており、C++テンプレートを使用して2つの異なる機能を実現しています.ここで、解像度コンポーネント自体は構文ツリーを構築していません.この作業は、TreeBuilderでやります.JSCには二つの利用可能なものがあります.TreeBuilderASTBuilderSyntaxChecker.後者は本質的には何もしない.これは解像器によって駆動され、解像度は停止するまで前に解析され得る.SyntaxCheckerが演じているのは、実際には文法検診器のようなものですから、名前は「文法検診器」と呼ばれています.SyntaxChecker.SyntaxCheckerが関数の末尾で動作を終了すると、関数本体の先頭(左の大括弧)と終了箇所(右の大括弧)の位置が保存されます.この記憶の範囲値は、その後、本格的な解析が開始されるときに使用されます.つまり、関数が本当に呼び出されたときにJSCは完全なソースコードが保存されていますので、保存範囲だけで大丈夫です.ソース文字列をコピーする必要はありません.
V 8(ChromeとNode.jsで使用)の場合も似ています.V 8では遅延解析に関するソースファイルがあります.
src/preparser.cc
src/preparser.h
src/preparser-api.cc
JSCとは違って、V 8の通常の解像度と遅延解析器は2つの異なるコードを使用しています.V 8の用語はPrePaserと言います.通常の解析器が1つの関数に解析されると、PrePaserを呼び出します.つまりParser::ParseFunctionLiteral方法を実行するときに、V 8は特殊な最適化をしています.この特例は、すぐに関数式(immediately invoked function expression略称IIIIIIFE)を呼び出すことで、グローバル変数を汚染することを心配することなく、より良い名前空間を提供することができます.
var foobar = (function() {
// // })();
このようなモードは今とても流行っていますので、V 8は簡単な打診法で関数のこのような使い方を検出します.functionキーワードの前に一つあります.(では、遅延解析を考慮しないで、この関数を直接解析します.例えば、上記のIIIFEの例です.
またSpider Monkey(FirefoxのJavaScriptエンジン)はどうですか?Spidentr Monkeyはまだ遅延解析を実現していませんが、すでに実現しています.bug 678037を見ると最新の進展が分かります.この取り組みはFirefoxの性能をさらに向上させます.
最後に言いたいのは、この文章は長いので、消化できないかもしれません.大体目を通してから詳しく読むことができます.これは「遅延読書」といいます.