アカデミック倧孊の教育プロゞェクトずしおのJITコンパむラヌ

箄16幎前、 Hotspotの最初のバヌゞョンがリリヌスされたした。JVM実装は、埌にSunのJREバンドルに付属する暙準の仮想マシンになりたした。

この実装の䞻な違いはJITコンパむラでした。これにより、倚くの堎合、 遅いJavaに関するステヌトメントは完党に受け入れられなくなりたした。
これで、CLR、Python、Ruby、Perl、さらに玠晎らしいプログラミング蚀語 Rなど、ほずんどすべおのむンタヌプリタヌプラットフォヌムが独自のJITトランスレヌタヌ実装を取埗したした。

この蚘事の䞀郚ずしお、産業甚JITコンパむラヌの実装に関するあたり知られおいない詳现に光を圓おる぀もりはありたせんが、基本的なこずの非垞に衚面的な玹介であり、関連するトピックに関するトレヌニングプロゞェクトに関するストヌリヌです。

したがっお、あなたは猫に興味があるかもしれたせん



JITコンパむルの利点


利点に぀いお話すには、たずそれが䜕であるかを理解したす。 遠くから始めたしょう。぀たり、コンパむルされた蚀語の定矩から始めたしょう。

コンパむルされたプログラミング蚀語ずは 、コンパむラによっお゜ヌスコヌドが特定のアヌキテクチャたずえばx86 / ARMのマシンコヌドに倉換されるプログラミング蚀語です。 このマシンコヌドは、プロセッサが完党に理解できる䞀連のコマンドであり、仲介者なしで実行できたす。

解釈されたプログラミング蚀語は、CPUで盎接実行するために゜ヌスがマシンコヌドに倉換されず、特別なむンタヌプリタヌプログラムを䜿甚しお実行されるずいう点で異なりたす。
䞭間オプションずしお、倚くの蚀語Java、C、Pythonがマシンに䟝存しないバむトコヌドに倉換されたす 。
CPUで盎接実行するこずはできず、実行するにはむンタプリタが必芁です。

なぜ解釈が遅いのですか この質問に察する答えは、奇劙なこずに、誰にずっおも明らかではありたせんが、それは非垞に単玔ですが、各コマンドを個別に解釈し、このコマンドのセマンティクスをプロセッサ蚀語に翻蚳するために远加のリ゜ヌスを費やしたす。 さらに、最新のプロセッサはシヌケンシャルコマンドの実行甚に最適化されおおり Conveyorを参照、ほずんどの堎合、むンタヌプリタヌは䞀連のオプションを備えた巚倧なスむッチであり、 遷移予枬を行き止たりに駆動したす。

バむトコヌドをプロセッサ蚀語に倉換し、実行のためにCPUに転送するこずは、これらの問題すべおに察する1回の自然な解決策です。 これは圌らが最も頻繁に行うこずであり、このプロセスをJust-in-time-compilation JITず呌びたす。 さらに、生成されたコヌドのさたざたな最適化が最も頻繁に実行されるのは、このフェヌズ䞭です。

さあ、緎習したしょう。

問題の声明


私たちの倧孊のコヌス「仮想化ず仮想マシン」は、ホットスポット、VirtualBox、NativeClientなどのVMのさたざたなクラスの開発に参加し、実装の詳现を盎接知っおいるニコラむ・むゎッティによっお教えられおいたす。 珟代のテクノロゞヌの玠晎らしさのおかげで、コヌスに぀いおは講矩宀で公開されおいるため、アカデミック倧孊の孊生である必芁はありたせん。 もちろん、コヌスの双方向性ず講矩で聎衆ず協力するため、これは少し間違っおいるこずに泚意する必芁がありたす。

コヌスの䞀環ずしお、詊隓の入孊資栌を取埗するために、孊生は単玔なスタック型仮想むンタヌプリタヌマシンずトランスレヌタヌの実装をCラむクな蚀語のバむトコヌドに曞き蟌むように求められたした。 実際、タスクは䞀芋思われるよりもやや単玔です。タスクず共に倚くの既補のC ++コヌドが発行されたす。

このタスク党䜓のコヌド名はmathvmです。これは、倚くの堎合、プログラミング蚀語の䞻な目的が䜕かを数えるこずであるためです。

兞型的なmathvmプログラム
function int fib(int x) { if (x <= 1) { return x; } int r = fib(x - 1); return r + fib(x - 2); } print(fib(35), '\n'); 

蚀語の䞻な機胜
最埌のポむントはタスクをもっず楜しくするように思えたす

気配りのある読者は、䜕らかの理由で、プログラムが最埌のアむテムから描画される1秒あたりのフレヌム数FPSを蚈算するこずに気付くでしょう。 この指暙により、通蚳者のパフォヌマンスを評䟡するこずができるため、誰かに退屈そうに思えた教育プロセスに競争心をもたらすこずができたす。
通蚳者がこれで最速であり、同様のベンチマヌクがいく぀か自動的に詊隓に合栌し、さらに同僚の愛ず尊敬を埗る孊生。 このゲヌムをプレむする最初のスレッドではないこずに泚意しおください。通垞、勝぀ためにはJITコンパむラを実装する必芁がありたした。

難しさ


単玔なバむトコヌド呜什のセマンティクスをx86呜什セットの察応する芁玠に単玔に倉換するこずは本質的に難しいように思われたすか

  1. コンパむルされた蚀語があれば、文字通りアセンブリ蚀語でテキストを生成し、それに応じおアセンブルしお実行可胜ファむルを䜜成できたす。
    しかし、JITブロヌドキャストのフレヌムワヌクでは、プロセスをこれらの2぀のフェヌズに分割するこずは蚱されない莅沢であり、VMプロセスのメモリでマシンコヌドをすぐに生成する方法を䜕らかの方法で孊習する必芁がありたす。
  2. たた、すべおのメモリ領域がマシンコヌドの蚘述に適しおいるわけではありたせん。 より正確には、どこかに曞き蟌むこずができたすが、実行可胜ずしおマヌクされたペヌゞを持぀メモリ領域にのみ実行を転送できたす。 単玔なmalloc'omではできないようです。
  3. 理想的には、もちろん、結果のマシンコヌドはできるだけ早く動䜜するはずです。

アスミット


Asmjitは、最初の2぀の問題を解決するのに非垞に適しおいたす。過去数幎間に1人が倧郚分を開発し、著者の他のプロゞェクトで䜿甚されたオヌプン゜ヌスラむブラリ、およびGTA San Andreas MPサヌバヌのスクリプトのJITです 。
名前が瀺すように、その䞻な䜿呜は、ゞャストむンタむムマシンコヌドを生成するための䟿利なAPIを䜜成するこずです。 そのパブリックむンタヌフェむスは、2぀の郚分に分けるこずができたす。

私の実装では、アセンブラヌを䜿甚したした。このアセンブラヌは、その透明性ずより優れた制埡性を実珟したした。

Asmjitを䜿甚した最も簡単な実䟋
 #include "asmjit/asmjit.h" #include <iostream> int main(int argc, char** argv) { using namespace std; using namespace asmjit; using namespace asmjit::x86; JitRuntime runtime; // RAII object X86Assembler assembler(&runtime); assembler.add(rdi, rsi); // add second argument to first assembler.mov(rax, rdi); // mov result to return register assembler.ret(); void * address = assembler.make(); cout << reinterpret_cast<int64_t (*) (int64_t, int64_t)>(address)(2, 2) << endl; // 4 return 0; } 

アセンブラヌに粟通しおいる人はあたり質問すべきではないず思いたす。 蚀及する唯䞀のものは、割り圓おられたメモリの寿呜に責任があるランタむムオブゞェクトです。 圌女は圌のデストラクタ RAII を呌び出した埌に解攟されたす。
より倚くの䜿甚䟋は、ラむブラリテストのあるディレクトリたたはリポゞトリにありたす。

デバッグ


奇劙なこずに、生成されたコヌドのデバッグは特に邪悪ではありたせん。
たず、䜕か問題が発生した堎合、Amsjitログを泚意深く芋るこずができたす。 ログでは、受け取ったアセンブラコヌドず、ここに衚瀺された堎所からのコメントを確認できたす。
これでも圹に立たない堎合は、最新のデバッガヌgdb / VSのほずんどで、逆アセンブリを䜿甚しおデバッグモヌドで動䜜する可胜性がありたす。 ぀たり、生成された関数ぞの呌び出しの代わりにブレヌクポむントを眮くこずができ、 Step Intoはアセンブラヌコヌドを䜿甚しお画面に転送したす。そこで、ほが通垞どおりステップごずにデバッグできたす。
重芁です


最適化


3日間のアクティブなプログラミングずデバッグの埌、最初の完党に掗緎されおいないバヌゞョンのJITトランスレヌタヌが登堎したした。このバヌゞョンでは、スタックず倉数のすべおの倀がメモリヌに毎回保存されおいたした。
そのような゜リュヌションでさえ、最倧6 FPSのむンタヌプリタヌで埗られた6 FPSで玄6倍のパフォヌマンスの向䞊をもたらしたした。

䞀般に、孊期の初め、ゲヌムのルヌルが明確になったずき、私はナポレオンの蚈画を立おたした。SSAでのバむトコヌド倉換ずスマヌトレゞスタ割り圓おアルゎリズムを䜿甚しお、すべおを倧人の方法で行うこずです。
しかし、時間ず平凡なco病の深刻な䞍足のために、すべおがやや平凡になりたした。

レゞスタ割り圓お

念のため、プログラムのパフォヌマンスに関しお最も重芁なポむントの1぀は、䜿甚可胜なCPUレゞスタを䜿甚する効率であるこずを思い出させおください。
これは、䞻な蚈算アクティビティがそれらでのみ実行可胜であるこずに加えお、さらに、L1キャッシュにあるメモリぞの読み取り/曞き蟌みも、レゞスタでの同様の操䜜よりも最倧2倍長く機胜するためです。
最も難しいのではなく、むしろ効果的なヒュヌリスティックな゜リュヌションを䜿甚したした仮想マシンスタックの最初の芁玠、汎甚スロット文字列/敎数の7芁玠、浮動小数点数のスロットの14芁玠をレゞスタに栌玍したす。
関数のフレヌムワヌクで最もホットな倉数は実際にスタックの䞀番䞋にあり、すべおの蚈算に関䞎するため、この゜リュヌションは最も正圓化されおいるようです。
さらに、関数を呌び出すずきに匕数のレむアりトず同じレゞスタを䜿甚するず、堎合によっおは呌び出しの堎所で時間を節玄できたす。
これらのアむデアを実装した結果、 9 FPSのアクセラレヌションが埗られ、45 FPSに達したした。

のぞき穎の最適化

生成に察する単玔な叀兞的アプロヌチの1぀は、いわゆるPeephole最適化です。これは、特定の呜什シヌケンスを芋぀けお他のより生産的な呜什に眮き換えるずいう考え方です。

たずえば、mathvmバむトコヌドの衚珟力が䞍足しおいるため、x0 <= x1のような比范挔算子は次のように説明する必芁がありたす。
 LOADIVAR_0 LOADIVAR_1 IF_CMPLE L1 ILOAD_0 //   0    JA L2 //   L1: ILOAD_1 L2: ... 

呜什の鑑定家x'86は、このようなコヌドははるかに短く、単䞀の条件付きゞャンプなしで蚘述できるず蚀いたす。
 mov rdi, %(var0) mov rsi, %(var1) cmp rdi, rsi setle al movzx rax, al 

他の堎合にも同様の構造がありたす吊定挔算子、forルヌプ。 これらのパタヌンの怜出ず修正により、 4 FPSから49ポむントに加速したした。

クロヌゞャヌの最適化


各関数のプロロヌグず゚ピロヌグでクロヌゞャヌを確実に機胜させるために、最埌の呌び出しの倉数がスタック倖の特別に割り圓おられたメモリ領域にあるアドレスを保存したした。
さらに、以前のベヌスポむンタヌを保存および埩元する必芁がありたす。 この喜びはすべお、メモリを積極的に䜿甚しおいる人を含め、各通話に最倧7぀の远加の指瀺を残したした。
実際、ほずんどの関数はそれ自䜓にクロヌゞャヌを含んでいないため、これらの損倱は回避できたす。
したがっお、短絡の存圚に぀いお受信したバむトコヌド党䜓の远加分析を実行し、察応する冗長な呜什が削陀された結果、挫画はさらに3 FPSを高速に描画し始めたした。

むンラむン化


マむクロ最適化に関するストヌリヌの最埌のポむントは、 むンラむン化たたはロシア語の「埋め蟌み」です。 産業甚コンパむラが小さな関数を呌び出すコストず栌闘する方法呌び出された関数のコヌドは、プロロヌグ/゚ピロヌグおよび他の「䞍芁な」呜什なしで、呌び出しの堎所で単玔に耇補されたす。 確かに、実際には、コンパむラは非垞に慎重に埋め蟌むずいう決定に近づいおいるこずに泚意する必芁がありたす-結果のマシンコヌドのサむズは、䜜業の品質の尺床でもありたす。

ただし、私の堎合は、掚定された時間であるため、埋め蟌たれたすべお非再垰呌び出しを埋め蟌むこずを詊みたした。
そしお、ここで、奇劙なこずに、ASTからバむトコヌドぞの倉換レベルで䜜業する方が簡単であるこずがわかりたした。

埗られた2 FPSの増加は、以前の堎合ほど重芁ではありたせんでしたが、合蚈53〜54 FPSほが9倍の加速は喜ばせざるを埗たせんでした。 同じ泚文の加速は、他の利甚可胜なベンチマヌクで芳察されたした。

興味深いこずに、私の単玔なJITトランスレヌタでさえ、むンタヌプリタヌをたったく起動せず、すべおのコヌドをすぐにコンパむルしたしたが、最高のパフォヌマンスを埗るにはりォヌムアップを数回繰り返したした。
これは、必芁なすべおがコンパむルされるだけでなく、プログラムのプロファむルを修正するために、ベンチマヌクでのりォヌムアップが必芁であるずいう事実に関する暩嚁ある人々の声明を瀺しおいたす。

たずめ


その結果、私の実装は最速になりたしたが、これは私が最もcな存圚であるずいう事実ずはあたり関係がありたせんが、JITの最も単玔な実装さえも䜜成したずいう事実はありたせん。
したがっお、少なくずもいく぀かのスポヌツの陰謀の欠劂は勝利を芆い隠した。

私の実装の゜ヌスは、 リポゞトリのimplディレクトリにありたす。
コヌドの品質に぀いお事前に謝眪したすが、これはただおもちゃのプロゞェクトであり、これ以䞊サポヌトする予定はありたせん。

ご枅聎ありがずうございたした

Source: https://habr.com/ru/post/J250841/


All Articles