C# JIT编译和.NET

96

我对JIT编译器的工作细节有点困惑。我知道C#会被编译成IL,第一次运行时就会被JIT编译。这是否涉及将其转换为本地代码?.NET运行时(作为虚拟机)是否与JIT编译后的代码交互?我知道这很幼稚,但我真的弄混了。我的印象一直是assemblies不会被.NET运行时解释,但我不明白交互的细节。

5个回答

93

是的,JIT将IL代码转换为本地机器指令。

.NET运行时与JIT编译的本地机器代码进行交互,这意味着运行时拥有本地机器代码占用的内存块,调用本地机器代码等。

您说的正确,.NET运行时不会解释您的程序集中的IL代码。

当执行到一个尚未被JIT编译成本地机器码的函数或代码块(例如if块的else子句)时,JIT将被调用以将该IL块编译为本地机器码。完成后,程序执行进入新生成的机器代码以执行其程序逻辑。如果在执行该本地机器代码时达到尚未编译为机器代码的函数调用,则会即时调用JIT编译该函数。如此反复。

JIT不一定会立即将函数体的所有逻辑编译为机器代码。如果函数具有if语句,则if或else子句的语句块可能直到执行通过该块时才被JIT编译。未执行的代码路径仍保留为IL形式,直到执行它们。

编译的本地机器代码保存在内存中,以便在下次执行该代码段时再次使用。第二次调用函数时,它将比第一次调用更快,因为第二次不需要进行JIT步骤。

在桌面.NET中,本地机器代码将保存在应用程序域的生命周期内存中。在.NET CF中,如果应用程序的内存不足,本地机器代码可能会被丢弃。它将从原始IL代码重新JIT编译,并在下次执行经过该代码时再次使用。


16
微小的严谨更正 - "JIT编译器不一定会编译函数体的所有逻辑" - 在理论实现中可能是这样,但在现有的.NET运行时实现中,JIT编译器将一次性编译整个方法。 - Paul Alexander
20
为什么要这样做呢?主要是因为非即时编译器不能假定在任何给定的目标平台上都可以使用某些优化。唯一的选择是编译到最低公共级别,或者更常见的是编译多个版本,每个版本针对自己的平台。JIT消除了这种不利之处,因为最终的机器码翻译是在目标机器上完成的,编译器知道可用的优化。 - Chris Shain
2
Chris,即使JIT知道有哪些优化可用,它也没有时间应用任何重要的优化。可能NGEN更强大。 - Grigory
3
@Grigory 是的,NGEN拥有JIT编译器没有的时间优势,但是有很多代码生成决策可以使JIT编译器改善编译代码的性能,这些决策不需要花费太长时间来解决。例如,JIT编译器可以选择指令集,以最好地匹配运行时发现的可用硬件,而不会增加JIT编译步骤的显著时间。我不认为.NET JITter在任何重要的方式上都做到了这一点,但这是可能的。 - dthorpe

29

代码被“编译”成类似汇编格式的 Microsoft Intermediate Language。

当您双击一个可执行文件时,Windows会加载 mscoree.dll,该文件设置CLR环境并启动您的程序代码。JIT编译器开始读取程序中的MSIL代码,并将代码动态编译为x86指令,CPU可以执行。


2
我将通过以下示例描述将IL代码编译为本机CPU指令。
(请注意,此处的“compilling”应更正为“compiling”)
public class Example 
{
    static void Main() 
    {
        Console.WriteLine("Hey IL!!!");
    }
}

主要CLR知道每个类型的详细信息以及从该类型调用哪个方法,这是由于元数据引起的。
当CLR开始将IL执行为本机CPU指令时,此时CLR为Main代码引用的每种类型分配内部数据结构。
在我们的案例中,我们只有一个类型Console,因此CLR将分配一个内部数据结构。通过该内部结构,我们将管理对所引用类型的访问。
在该数据结构内部,CLR具有关于该类型定义的所有方法的条目。每个条目都保存可以找到该方法实现的地址。
在初始化此结构时,CLR在其内部包含的未记录的FUNCTION中设置了每个条目。正如您可以猜测的那样,这个FUNCTION就是我们所谓的JIT编译器。 总体而言,您可以将JIT编译器视为CLR函数,它将IL编译为本机CPU指令。让我详细介绍一下在我们的示例中该过程是如何进行的。
1.当Main第一次调用WriteLine时,将调用JITCompiler函数。
2.JIT编译器函数知道正在调用哪个方法以及定义此方法的类型。
3.然后,Jit编译器搜索定义该类型的程序集,并获取该类型定义的方法的IL代码,在我们的例子中是WriteLine方法的IL代码。
4.JIT编译器分配动态内存块,然后JIT验证并将IL代码编译为本机CPU代码,并将该CPU代码保存在该内存块中。
5.然后,JIT编译器返回到内部数据结构条目,并用包含WriteLine的本机CPU指令的新动态创建的内存块的地址替换地址(这些地址最初引用WriteLine的IL代码实现)。
6.最后,JIT编译器函数跳转到内存块中的代码,并执行writeline方法的本机代码。
7.在执行WriteLine之后,代码返回到正常执行的Mains'code。

1
.NET 使用一种称为 MSIL 的中间语言,有时缩写为 IL。编译器读取您的源代码并生成 MSIL。运行程序时,.NET Just In Time (JIT) 编译器会读取您的 MSIL 代码并在内存中生成可执行应用程序。您看不到这些过程,但了解幕后发生的事情是一个好主意。

0

.NET Framework使用CLR环境来生成MSIL(Microsoft Intermediate Language),也称为IL。 编译器读取您的源代码,当您构建/编译项目时,它会生成MSIL。 现在,当您最终运行项目时,.NET JIT(即时编译器)开始工作。 JIT读取您的MSIL代码并生成本机代码(即x86指令),可以轻松地由CPU执行。JIT读取所有MSIL指令并逐行执行。

如果您有兴趣了解幕后发生的事情,已经有人回答了。请参阅-这里


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接