Baseline コンパイラを導入しました

原文: The Baseline Compiler Has Landed on April 5, 2013 by Kannan Vijayan

水曜日に、私たちは Firefox Nightly に Baseline コンパイラを投入しました。始めから終わりまで 6 か月の作業の後、私たちはついに苦労の末の産物をメインリリースストリームにマージできます。

Baseline コンパイラとは何か?

Baseline (いいえ、これに *Monkey のコードネームはありません) は、IonMonkey の新たなウォームアップコンパイラです。これは短期的にはパフォーマンス向上を、また長期的には新たなパフォーマンス向上の機会をもたらします。それは JaegerMonkey を不要にするための扉を開き、そして SpiderMonkey のメモリ使用量を大きく削減するという別の変化を与えることも可能にするでしょう。新たな言語機能の第一段階の最適化を実装することを容易かつ迅速にするとともに、それらを IonMonkey での高い段階の最適化に改良することを容易にします。

Kraken、Sunspider、Octane ベンチマークのスコアは投入段階で 5-10% 向上しており、また SpiderMonkey をよりよくするための Baseline の活用を続けることによる改善を続けます。AreWeFastYet の Web サイトをご覧ください。グラフは範囲選択 (クリック & ドラッグする) することで拡大できます。

別の JIT? なぜ別の JIT?

現在まで、Firefox は 2 つの JIT を使用していました: JaegerMonkey と IonMonkey です。Jaeger は “かなり高速な” 汎用の JIT で、Ion は “実に高速な” 強力に最適化する JIT です。最初のうちはホットコードが Jaeger でコンパイルされ、それが本当にホットである場合は Ion で再コンパイルされます。この手法は段階的なコードの最適化を可能にするので、確実にホットなコードに対してとても重厚なコンパイルを行います。しかしこの手法の成否は、さまざまな段階のコンパイルで費やす時間と各段階で得られる実際のパフォーマンス向上との間でよいバランスをとることにかかっています。

簡単に言うと、現在は JaegerMonkey を IonMonkey 用の一時しのぎのベースラインコンパイラとして使用していますが、そのような処理向けには設計されていません。Ion は Ion のことを考慮したベースラインコンパイラを必要としており、それが Baseline とは何かの答えです。

より充実した説明では、いつものように微妙なニュアンスの違いがあります。これから、それを 3 つの章で見ていきます: 現行リリースでの動作、なぜ問題であるのか、Baseline はどのようにして問題の修正を助けるか。

現在の事実

簡単に言うと、こちらが現行リリースの Firefox の JIT コンパイル手法です:

  1. すべての JavaScript 関数はインタプリタで実行し始めます。インタプリタは実に遅いのですが、JIT で使用する型情報を収集します。
  2. 関数がややホットになると、JaegerMonkey でコンパイルされます。Jaeger は生成した JIT コードの最適化に、収集された型情報を使用します。
  3. 関数は Jaeger の JIT コードで実行されます。それがさらにホットになると、IonMonkey で再コンパイルされます。IonMonkey のコンパイラは JaegerMonkey より多くの時間を費やして、実に最適化された JIT コードを生成します。
  4. 関数の型情報が替わった場合は、既存の JIT コード (Jaeger のものと Ion のもの両方) が破棄されて関数はインタプリタ実行に戻り、JIT のライフサイクル全体を再度たどります。

SpiderMonkey の JIT コンパイル方法がこのように構成されている、よい理由があります。

おわかりのとおり、Ion は高度に最適化されたコードを生成するためコンパイルに長い時間をかけて、重厚な最適化手法をたくさん適用します。これは、Ion のコンパイルを過度に早く働かせるとコンパイル後に型情報が替わりやすくなり、Ion のコードが無効になることが多くなるかもしれないということです。それはエンジンで、破棄されるであろうコンパイルにかけた多くの時間全体が無駄になることを引き起こします。一方、コンパイルまであまりに長く待つと、コンパイルまでに関数をインタプリタ実行するところで過度に時間を費やすでしょう。

JaegerMonkey の JIT コンパイラは IonMonkey の JIT コンパイラほどの時間を費やしません。Jaeger は収集された型情報をコード生成の最適化に使用しますが、Ion が生成したコードを最適化する際ほど多くの時間は費やしません。こちらは “かなり高速な” JIT コードを生成しますが、Ion より高速な生成方法です。

よって Jaeger はインタプリタと Ion の間に挟まっており、確実にホットなコードは Ion でコンパイルされてより高速になり、またややホットなコードは Jaeger でコンパイルされます (型情報の変更によりたびたび再コンパイルされますが、Jaeger はコンパイルが高速なので問題ありません)。

この手法は SpiderMonkey がパフォーマンスを犠牲にするインタプリタで実行する時間が可能な限り少ないようにするのと併せて、確実にホットな JavaScript コード向けの Ion によるコード生成の利点を得るようにします。よってすべてがうまくいく、そうでしょうか?

いいえ。そうではありません。

問題点

上記の手法はすばらしい初期の折衷案ですが、いくつか重大な問題を引き起こしています:

  1. JaegerMonkey も IonMonkey も型情報を収集しませんが、型情報に頼って JIT コードを生成していました。それらは、JIT コードに関連づけられた型情報が安定している限り実行されます。型情報が変化すると JIT コードは無効にされて、さらに型情報を収集するためインタプリタ実行に戻されます。
  2. Jaeger と Ion の呼び出し規則は異なっていました。Jaeger はヒープに割り当てられたインタプリタのスタックを直接使用したのに対して、Ion は (より高速な) ネイティブ C スタックを使用しました。これは、Jaeger と Ion のコード間の呼び出しを非常に高コストにしました。
  3. インタプリタで収集された型情報は、ある特定の状況で制限されました。既存の型推定 (TI) システムは、ある種の型情報をうまく取り込みました (例えば、コード内の指定された場所で読み取るプロパティから見えると考えられる値の型) が、別の種類の型情報についてはとても下手でした (例えば、プロパティが取得されていたオブジェクトの型)。これは、Ion が実行可能な種類の最適化を制限しました。
  4. TI の基盤は、型の分析情報を永続的に追跡するために追加のメモリを多く要求しました (今でも要求します)。初めに TI の設計や実装を行った Brian Hackett 氏は Ion 向けのメモリオーバーヘッドを劇的に削減できると考えましたが、それは Jaegar 向けに行うのよりもはるかに多くの時間がかかるようでした。
  5. 多くの Web コードは、Jaegar によるコンパイル段階へ入るのにも不十分なほどホットではありません。Jaeger は Ion よりコンパイルにかかる時間が短いのですがそれでも高コストであり、また出力されたコードは常に型情報の変化により破棄される可能性がありました。このため、Jaegar によるコンパイルのしきい値はいまだにかなり高く設定されており、それゆえ多くのホットではないコードがインタプリタで実行されました。例えば SpiderMonkey はこの問題のために、SunSpider ベンチマークで大きく出遅れていました。
  6. Jaeger は実に複雑であり、扱うのが困難です。

解決策

Baseline コンパイラは、これらの短所を解決するよう設計されました。Baseline の JIT コードはインタプリタのように既存の TI エンジンに情報を与えます。さらに、インラインキャッシュ (IC) チェーンを使用することでより多くの情報を収集します。 実行中に Baseline の JIT コードが作成する IC チェーンは Ion によって調査されて、Ion の JIT コードをより最適化するために使用されます。Baseline の JIT コードは無効にならず、また再コンパイルも不要です。動的な変化を追跡およびそれに対応して、必要に応じて IC チェーンの新たなスタブを追加します。Baseline のネイティブコンパイルと最適化された IC スタブは、インタプリタより 10 倍から 100 倍高速な実行を可能にします。また Baseline は Ion の呼び出し規則を踏襲して、インタプリタスタックに代わり C スタックを使用します。最後に、ベースラインコンパイラの設計は JaegerMonkey や IonMonkey よりとてもシンプルであり、IonMonkey と共通のコードを多数共有しす (例えばアセンブラ、JIT コードコンテナ、トランポリンなど)。またこれは、Baseline が新たな型情報を収集したり、新たなケースの最適化を行ったりするように拡張することを実に容易にします。

要するに、Baseline はインタプリタと JIT の間のよりよい中間層を提供します。Baseline は動的なコードに対してインタプリタのように安定的かつ弾力的で、高い段階の JIT に提供する型情報を収集して、また新機能を扱うための更新が容易です。しかし JIT として一般的なケース向けの最適化を行って、インタプリタより大幅な高速化を実現します。

私たちはどこへ進むのか?

今は Baseline を有効にする重要で大きな変化の一握りであり、これから注視していくことがあります:

  • 型推定で使用するメモリの削減によるメモリ消費の大幅な低減
  • 型推定がインライン関数のよりよい最適化を可能にすることによるパフォーマンス向上
  • IonMonkey と Baseline のさらなる統合により、高度に多相的なオブジェクト操作コードのパフォーマンスを向上させる
  • getter/setter、proxy、ジェネレータといった高度な機能のさらなる最適化

また、最近の出来事についてもお話ししましょう… asm.js や OdinMonkey を取り巻くニュースによる波乱を受けて、高度な JavaScript が最適化の観点で劣った存在になることに関する懸念が (重要な意見として) 浮かび上がりました。Baseline の投入と継続的な作業が、JS チームが高度でとても動的な JavaScript をできるだけ早くすることに関心を持ち、また持ち続けるために説得力がある合図として仕えることを少しでも望んでいます。

謝辞

Baseline は Jan De Mooij と私 (訳注: Kannan Vijayan 氏) が、Tom Schuster 氏と Brian Hackett 氏による重要な貢献とともに開発しました。開発作業は、すばらしいファジングテスターである Christian Holler 氏と Gary Kwong 氏にとても助けられました。

もちろん、Baseline はそれ自身では目的を果たさないことは特筆しなければなりません。IonMonkey チームによってすばらしい作業が行われ、また他の JS チームは Baseline が存在する理由を与えてくれます。

 

(4/10 訳注追記: 文中で「Javas」と記載している箇所がありましたが、「JavaScript」の誤りですので修正しました。ご指摘ありがとうございました。)

2 件のコメント

  1. Average :

    問題点の4の文中
    「TI の基盤は、型の分析情報を永続的に追跡するために追加のメモリを多く要求しました (今でも要求します)。初めに IT の設計や実装を行った」
    とありますが、
    この文の「ITの設計や実装を行った」は「TIの設計や実装を行った」ではないでしょうか。
    原文も多分そういう風になっているように見えます。

    1. yyss :

      ご指摘ありがとうございます。「TI」に修正しました。
      誤って逆に打ったのを見落としていたようです。