C#JITコンパイルと.NET


86

JITコンパイラの動作の詳細について少し混乱しました。私はC#がILにコンパイルされることを知っています。初めて実行されるときはJITされます。これには、ネイティブコードに変換されることが含まれますか?.NETランタイム(仮想マシンとして?)はJITされたコードと相互作用しますか?私はこれが素朴であることを知っていますが、私は本当に自分自身を混乱させました。私の印象では、アセンブリは.NETランタイムによって解釈されませんが、相互作用の詳細はわかりません。

回答:


83

はい、ILコードのJITには、ILをネイティブマシン命令に変換することが含まれます。

はい。.NETランタイムは、ランタイムがネイティブマシンコードによって占有されているメモリブロックを所有しているという意味で、JITされたネイティブマシンコードと相互作用します。ランタイムはネイティブマシンコードを呼び出します。

.NETランタイムがアセンブリ内のILコードを解釈しないのは正しいことです。

実行がまだネイティブマシンコードにJITコンパイルされていない関数またはコードブロック(ifブロックのelse句など)に到達すると、JIT'rが呼び出されてILのそのブロックがネイティブマシンコードにコンパイルされます。 。それが完了すると、プログラムの実行は、新しく発行されたマシンコードを入力して、そのプログラムロジックを実行します。そのネイティブマシンコードの実行中に、マシンコードにまだコンパイルされていない関数への関数呼び出しに到達すると、JIT'rが呼び出され、その関数が「ジャストインタイム」でコンパイルされます。等々。

JIT'rは、必ずしも関数本体のすべてのロジックを一度にマシンコードにコンパイルするわけではありません。関数にifステートメントがある場合、ifまたはelse節のステートメントブロックは、実行が実際にそのブロックを通過するまでJITコンパイルされない場合があります。実行されなかったコードパスは、実行されるまでIL形式のままになります。

コンパイルされたネイティブマシンコードはメモリに保持されるため、次にコードのそのセクションを実行するときに再び使用できます。2回目に関数を呼び出すと、2回目にJITステップが不要になるため、最初に呼び出すときよりも高速に実行されます。

デスクトップ.N​​ETでは、ネイティブマシンコードはappdomainの存続期間中メモリに保持されます。.NET CFでは、アプリケーションのメモリが不足している場合、ネイティブマシンコードが破棄される可能性があります。次回実行がそのコードを通過するときに、元のILコードから再度JITコンパイルされます。


14
マイナーなペダンティック修正-'JIT'rは必ずしも関数本体のすべてのロジックをコンパイルするわけではありません '-理論的な実装ではそうかもしれませんが、.NETランタイムの既存の実装では、JITコンパイラは全体をコンパイルします一度にすべてのメソッド。
ポールアレクサンダー

18
これが行われる理由については、主に、非jitコンパイラーが、特定の最適化が特定のターゲットプラットフォームで利用可能であると想定できないためです。唯一のオプションは、最小公分母にコンパイルするか、より一般的には、それぞれが独自のプラットフォームを対象とする複数のバージョンをコンパイルすることです。JITは、マシンコードへの最終的な変換がターゲットマシンで行われるため、コンパイラが利用可能な最適化を認識しているため、この欠点を排除します。
Chris Shain 2011

1
クリスは、JITが利用可能な最適化を知っているとはいえ、重要な最適化を適用する時間はありません。おそらくNGENの方が強力です。
グリゴリー

2
@Grigoryはい、NGENにはJITコンパイラにはない時間の余裕がありますが、コンパイルされたコードのパフォーマンスを向上させるためにJITが行うことができるcodegenの決定はたくさんあり、理解するのにそれほど時間はかかりません。たとえば、JITコンパイラは、JITコンパイルステップに大きな時間を追加することなく、実行時に検出された使用可能なハードウェアに最適な命令セットを選択できます。.NET JITterがこれを重要な方法で行うとは思いませんが、可能です。
dthorpe 2013年

24

コードは、アセンブリ形式に似たMicrosoft中間言語に「コンパイル」されます。

実行可能ファイルをダブルクリックすると、Windowsが読み込まmscoree.dllれ、CLR環境がセットアップされ、プログラムのコードが開始されます。JITコンパイラは、プログラム内のMSILコードの読み取りを開始し、CPUが実行できるx86命令にコードを動的にコンパイルします。


1

.NETは、MSILと呼ばれる中間言語を使用します。ILと略されることもあります。コンパイラはソースコードを読み取り、MSILを生成します。プログラムを実行すると、.NET Just In Time(JIT)コンパイラがMSILコードを読み取り、メモリ内に実行可能アプリケーションを生成します。これが発生することはありませんが、舞台裏で何が起こっているのかを知ることをお勧めします。


1

以下の例を使用して、ILコードをネイティブCPU命令にコンパイルする方法について説明します。

public class Example 
{
    static void Main() 
    {
        Console.WriteLine("Hey IL!!!");
    }
}

主にCLRは、タイプに関するすべての詳細と、そのタイプから呼び出されているメソッドがメタデータに起因することを認識しています。

CLRがネイティブCPU命令にILを実行し始めると、CLRはMainのコードによって参照されるすべてのタイプに内部データ構造を割り当てます。

この場合、タイプコンソールは1つしかないため、CLRはその内部構造を介して1つの内部データ構造を割り当て、参照されるタイプへのアクセスを管理します。

そのデータ構造内に、CLRにはそのタイプで定義されたすべてのメソッドに関するエントリがあります。各エントリは、メソッドの実装を見つけることができるアドレスを保持します。

この構造を初期化すると、CLRは、CLR自体に含まれる文書化されていないFUNCTIONの各エントリを設定します。ご想像のとおり、このFUNCTION はJITコンパイラと呼ばれます

全体として、JITコンパイラはILをネイティブCPU命令にコンパイルするCLR関数と見なすことができます。この例でこのプロセスがどのようになるかを詳しく説明します。

1. MainがWriteLineを最初に呼び出すと、JITCompiler関数が呼び出されます。

2.JITコンパイラ関数は、呼び出されているメソッドと、このメソッドを定義するタイプを認識しています。

3.次に、Jitコンパイラは、そのタイプが定義されているアセンブリを検索し、そのタイプで定義されているメソッドのILコード(この場合はWriteLineメソッドのILコード)を取得します。

4.JITコンパイラはDYNAMICメモリブロックを割り当てます。その後、JITはILコードを検証してネイティブCPUコードにコンパイルし、そのCPUコードをそのメモリブロックに保存します。

5.次に、JITコンパイラは内部データ構造エントリに戻り、アドレス(主にWriteLineのILコード実装を参照)を、WriteLineのネイティブCPU命令を含む動的に作成された新しいメモリブロックのアドレスに置き換えます。

6.最後に、JITコンパイラ関数はメモリブロック内のコードにジャンプします。このコードは、WriteLineメソッドの実装です。

7.WriteLineの実装後、コードはMainsのコードに戻り、通常どおり実行を継続します。


0

.NET Frameworkは、CLR環境を使用して、ILとも呼ばれるMSIL(Microsoft中間言語)を生成します。コンパイラはソースコードを読み取り、プロジェクトをビルド/コンパイルすると、MSILが生成されます。これで、最終的にプロジェクトを実行すると、.NET JIT(ジャストインタイムコンパイラ)が動作します。JITはMSILコードを読み取り、CPUで簡単に実行できるネイティブコード(x86命令)を生成します。JITはすべてのMSIL命令を読み取り、それらを1行ずつ実行します。

舞台裏で何が起こっているのかを知りたい場合は、すでに回答済みです。フォローしてください-ここ

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.