背景

现代软件编程语言(如C#和VB.NET)使用的是人类友好的语法,计算机无法直接理解。这种对人类友好的语法中的软件命令称为“源代码”。在计算机执行源代码之前,称为编译器的特殊程序必须将 其重写为机器指令,也称为目标代码。该过程(通常简称为“编译”)可以显式或隐式完成。

显式编译

显式编译在程序执行之前将上层语言转换为目标代码。提前(AOT)编译器旨在确保CPU在进行任何交互之前可以理解代码中的每一行。

隐式编译

隐式编译是一个两步过程。第一步是通过特定语言的编译器将源代码转换为中间语言(IL)。第二步是将IL转换为机器指令。与显式编译器的主要区别在于,在运行时仅将执行的IL代码片段编译为机器指令。.NET框架将此编译器称为JIT(即时)编译器。

可移植性

在开发针对各种平台的程序时,提供可移植性是一个关键方面。有几个问题需要答案才能在多个平台上执行:

  • 使用哪种CPU?
  • 该程序将在什么操作系统(OS)上运行?

为了使软件发挥最大作用,必须使用各种显式编译器来编译源代码。
隐式方式可以毫不费力地提供可移植性,因为该过程的第一步与平台无关。每个目标平台都有一个JIT编译器,并且只要可以解释IL,程序就可以执行。最初的编译器不需要知道软件可能运行的所有位置。

即时编译

JIT编译器是公共语言运行时(CLR)的一部分。CLR管理所有.NET应用程序的执行。除了在运行时进行JIT编译外,CLR还负责垃圾收集类型安全异常处理
.NET即时编译 - 图1图1
不同的机器配置使用不同的机器级别指令。正如图1所示,源代码编译为EXEDLL由.NET编译器。通用中间语言(CIL)包含任何支持.NET的环境都可以执行的指令,并包括描述数据和代码结构的元数据。JIT编译器将CIL指令处理为特定于环境的机器代码。利用源代码中的CIL指令可确保程序的可移植性。JIT编译器仅编译在运行时调用的那些方法。它还跟踪通过方法传递的任何变量或参数,并在.NET Framework的运行时环境中强制执行类型安全性。
.NET框架中的JIT编译有三种类型:

正常的JIT编译

使用Normal JIT编译器(图2 ),可以在运行时调用方法进行编译。执行后,此方法存储在内存中,通常称为“加急”。相同的方法不需要进一步的编译。后续方法调用可直接从内存缓存访问。
.NET即时编译 - 图2图2

Econo JIT编译

所述的Econo JIT编译器被显示在图3中它在运行时调用时会编译方法,并在执行后将其从内存中删除。
.NET即时编译 - 图3
图3

JIT之前的编译

.NET中的另一种编译形式称为JIT前编译。它编译整个程序集而不是使用的方法。在.NET语言中,这是在Ngen.exe(本机图像生成器)中实现的。如图4所示,所有CIL指令在启动前都会编译为本机代码。这样,运行时可以使用缓存中的本机映像,而不用调用JIT编译器。
.NET即时编译 - 图4
图4

利弊

隐式和显式编译都有优点和缺点。

  • 提前(AOT)提供了更快的启动时间,尤其是在大型应用程序中启动时执行大量代码的情况下。但是它需要更多的磁盘空间和更多的内存/虚拟地址空间,才能同时保留IL和预编译的映像。在这种情况下,JIT编译器必须执行许多磁盘I / O操作,这非常昂贵。
  • JIT可以生成更快的代码,因为它针对当前的执行平台。AOT编译必须以所有可能执行平台中的最低公分母为目标。
  • JIT可以在应用程序运行时对其进行概要分析,并动态重新编译代码以在热路径(最常用的功能)中提供更好的性能。

原文地址:https://www.telerik.com/blogs/understanding-net-just-in-time-compilation