【问题标题】:C# JIT compiling and .NETC# JIT 编译和 .NET
【发布时间】:2011-04-08 01:17:57
【问题描述】:

我对 JIT 编译器如何工作的细节有些困惑。我知道 C# 编译为 IL。第一次运行它是 JIT 的。这是否涉及将其翻译为本机代码? .NET 运行时(作为虚拟机?)是否与 JIT 代码交互?我知道这很幼稚,但我自己真的很困惑。我的印象一直是.NET 运行时不解释程序集,但我不了解交互的细节。

【问题讨论】:

    标签: c# jit


    【解决方案1】:

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

    是的,.NET 运行时与 JIT 的本机机器代码交互,从某种意义上说,运行时拥有本机机器代码占用的内存块,运行时调用本机机器代码等。

    .NET 运行时不解释程序集中的 IL 代码是正确的。

    当执行到达尚未被 JIT 编译为本机机器代码的函数或代码块(如 if 块的 else 子句)时,会调用 JIT'r 以将该 IL 块编译成本机机器代码。完成后,程序执行进入新发出的机器代码以执行其程序逻辑。如果在执行该本机机器代码执行时遇到对尚未编译为机器代码的函数的函数调用,则调用 JIT'r 以“及时”编译 that 函数。以此类推。

    JIT'r 不一定将函数体的所有逻辑一次编译成机器代码。如果函数有 if 语句,则 if 或 else 子句的语句块可能不会被 JIT 编译,直到执行实际通过该块。未执行的代码路径在执行之前保持为 IL 形式。

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

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

    【讨论】:

    • 轻微的迂腐修正 - 'JIT'r 不一定编译函数体的所有逻辑' - 在可能的理论实现中,但在 .NET 的现有实现中运行时,JIT 编译器将一次编译所有方法。
    • 至于为什么这样做,主要是因为非jit编译器不能假设某些优化在任何给定的目标平台上都可用。唯一的选择是编译到最低公分母,或者更常见的是编译多个版本,每个版本都针对自己的平台。 JIT 消除了这个缺点,因为最终转换为机器代码是在目标机器上完成的,编译器知道哪些优化可用。
    • 克里斯,即使 JIT 知道有哪些优化可用,它也没有时间应用任何重要的优化。可能 NGEN 更强大。
    • @Grigory 是的,NGEN 拥有 JIT 编译器所没有的大量时间,但是 JIT 可以做出很多代码生成决策来提高编译代码的性能很多时间来弄清楚。例如,JIT 编译器可以选择指令集以最好地匹配在运行时找到的可用硬件,而不会增加 JIT 编译步骤的大量时间。我不认为 .NET JITter 以任何重要的方式做到这一点,但这是可能的。
    【解决方案2】:

    代码被“编译”成类似于汇编格式的微软中间语言。

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

    【讨论】:

      【解决方案3】:

      我将通过下面的示例描述如何将 IL 代码编译为原生 CPU 指令。

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

      CLR 主要知道关于类型的所有细节以及从该类型调用的方法,这是由于元数据。

      当 CLR 开始在本地 CPU 指令中执行 IL 时,那时 CLR 会为 Main 代码引用的每个类型分配内部数据结构。

      在我们的例子中,我们只有一种类型的控制台,因此 CLR 将分配一个内部数据结构。通过该内部结构,我们将管理对引用类型的访问。

      在该数据结构中,CLR 包含有关该类型定义的所有方法的条目。每个条目都包含可以找到方法实现的地址。

      当初始化这个结构时,CLR 将每个条目设置在 CLR 自身内部包含的未记录的 FUNCTION 中。你可以猜到,这个FUNCTION 就是我们所说的JIT Compiler。

      总体而言,您可以将 JIT 编译器视为一个 CLR 函数,它将 IL 编译为本地 CPU 指令。在我们的示例中,让我详细向您展示这个过程。

      1.当Main第一次调用WriteLine时,JITCompiler函数被调用。

      2.JIT Compiler 函数知道正在调用什么方法以及定义该方法的类型。

      3. 然后,Jit 编译器搜索定义该类型的程序集并获取该类型定义的方法的 IL 代码,在我们的案例中为 WriteLine 方法的 IL 代码。

      4.JIT编译器分配DYNAMIC内存块,然后JIT验证IL代码并将其编译为本地CPU代码,并将该CPU代码保存在该内存块中。

      5.然后JIT编译器回到内部数据结构入口,将地址(主要参考WriteLine的IL代码实现)替换为地址新动态创建的内存块,其中包含WriteLine的本机CPU指令。

      6.最后,JIT Compiler函数跳转到内存块中的代码,执行writeline方法的native代码。

      7.WriteLine执行后,代码返回Mains的代码,继续正常执行。

      【讨论】:

        【解决方案4】:

        .NET 使用称为 MSIL 的中间语言,有时缩写为 IL。编译器读取您的源代码并生成 MSIL。当您运行该程序时,.NET 即时 (JIT) 编译器会读取您的 MSIL 代码并在内存中生成可执行应用程序。您不会看到任何这种情况发生,但最好了解幕后发生的事情。

        【讨论】:

          【解决方案5】:

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

          如果您有兴趣了解幕后发生的事情,已经得到解答。请关注-Here

          【讨论】:

            猜你喜欢
            • 2013-09-01
            • 1970-01-01
            • 1970-01-01
            • 2010-09-21
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-03-06
            相关资源
            最近更新 更多