JITコンパイラーの動作の詳細について少し混乱しました。 C#がILにコンパイルされることを知っています。初めて実行されると、JITされます。これには、ネイティブコードに変換されることが含まれますか? .NETランタイム(仮想マシンとして)はJITコードと対話しますか?私はこれが素朴であることを知っていますが、私は本当に混乱しています。私の印象では、アセンブリは.NETランタイムによって解釈されませんが、相互作用の詳細は理解していません。
はい、JITのILコードには、ILをネイティブマシン命令に変換することが含まれます。
はい、.NETランタイムはJITされたネイティブマシンコードと対話します。つまり、ランタイムがネイティブマシンコードによって占有されているメモリブロックを所有しているという意味で、ランタイムはネイティブマシンコードを呼び出します。
.NETランタイムがアセンブリのILコードを解釈しないことは正しいです。
実行は、ネイティブマシンコードにまだJITコンパイルされていない関数またはコードブロック(ifブロックのelse節など)に到達すると、JIT'rが呼び出され、ILのブロックをネイティブマシンコードにコンパイルします。それが完了すると、プログラムの実行は新しく発行されたマシンコードに入り、プログラムロジックを実行します。ネイティブマシンコードの実行中に、まだマシンコードにコンパイルされていない関数の関数呼び出しに到達すると、JIT'rが呼び出されてthat function "just in time"がコンパイルされます。等々。
JIT'rは、必ずしも関数本体のすべてのロジックを一度にマシンコードにコンパイルするとは限りません。関数にifステートメントがある場合、ifまたはelse節のステートメントブロックは、実行が実際にそのブロックを通過するまでJITコンパイルされない場合があります。実行されなかったコードパスは、実行されるまでIL形式のままです。
コンパイルされたネイティブマシンコードはメモリに保持されるため、次回コードのセクションを実行するときに再び使用できます。 2回目に関数を呼び出すと、最初に呼び出すよりも速く実行されます。これは、2回目にJITステップが必要ないためです。
デスクトップ.NETでは、ネイティブドメインのコードはappdomainの有効期間中メモリに保持されます。 .NET CFでは、アプリケーションのメモリが不足している場合、ネイティブマシンコードが破棄される場合があります。次に実行がそのコードを通過するときに、元のILコードから再度JITコンパイルされます。
コードは、Microsoft中間言語に「コンパイル」されます。これは、アセンブリ形式に似ています。
実行可能ファイルをダブルクリックすると、Windowsはmscoree.dll
その後、CLR環境をセットアップし、プログラムのコードを開始します。 JITコンパイラは、プログラム内のMSILコードの読み取りを開始し、CPUが実行できるx86命令にコードを動的にコンパイルします。
.NETはMSILと呼ばれる中間言語(ILと略されることもあります)を使用します。コンパイラはソースコードを読み取り、MSILを生成します。プログラムを実行すると、.NET Just In Time(JIT)コンパイラがMSILコードを読み取り、メモリ内に実行可能アプリケーションを生成します。これが発生することはありませんが、舞台裏で何が起こっているかを知ることは良い考えです。
.NET Frameworkは、CLR環境を使用して、ILとも呼ばれるMSIL(Microsoft Intermediate Language)を生成します。コンパイラはソースコードを読み取り、プロジェクトをビルド/コンパイルすると、MSILが生成されます。さて、最終的にプロジェクトを実行すると、.NET JIT( Just-in-time Compiler )が実行されます。 JITはMSILコードを読み取り、CPUで簡単に実行できるネイティブコード(x86命令)を生成します。JITはすべてのMSIL命令を読み取り、それらを行ごとに実行します。
あなたが舞台裏で何が起こるかを見たい場合、それはすでに答えられています。フォローしてください- ここ
以下の例で、ILコードをネイティブCPU命令にコンパイルする方法を説明します。
public class Example
{
static void Main()
{
Console.WriteLine("Hey IL!!!");
}
}
主にCLRは、型に関するすべての詳細と、その型から呼び出されるメソッドがメタデータによるものであることを知っています。
CLRがILをネイティブCPU命令にILを実行し始めたとき、CLRはMainのコードによって参照されるすべてのタイプに内部データ構造を割り当てます。
私たちの場合、コンソールは1つのタイプしかないため、CLRはその内部構造を介して1つの内部データ構造を割り当て、参照されたタイプへのアクセスを管理します
そのデータ構造CLR内には、そのタイプで定義されたすべてのメソッドに関するエントリがあります。各エントリには、メソッドの実装が見つかるアドレスが保持されます。
この構造を初期化するとき、CLRは、CLR自体に含まれる文書化されていない[〜#〜] function [〜#〜]の各エントリを設定します。 this[〜#〜] 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 'コードに戻り、通常どおり実行を継続します。