深入解析JIT编译

SoftNight 系统安全 2018年11月1日发布
Favorite收藏

导语:JIT的全称是Just-In-Time,即时的意思。JIT编译器是执行JIT编译的工具,它是.NET应用程序的一个重要特性,今天我们来深入分析一下。

你知道JIT这个技术术语吗?JIT的全称是Just-In-Time,即时的意思。JIT编译器是执行JIT编译的工具,它是.NET应用程序的一个重要特性,今天我们来深入分析一下。

如何让托管应用程序可移植?

我们知道.NET Framework和CLR(通用语言运行库)为针对该平台的应用程序提供了很多有用的功能,比如自动内存管理。不过,提出托管运行时的一个主要目的就是实现应用的可移植。

那么应用程序是可移植的意味着什么呢?这意味着,首先,它可以在任何类型的硬件上运行。理想情况下,它也应该与软件无关(特别是与操作系统无关),例如微软创建的ans正在积极开发一个多平台的  .NET Core。

这种可移植性不仅使应用程序可以在任何硬件或软件平台上启动,而且还使开发人员不必关心底层的低级结构。例如,当使用TPL(任务并行库)时,程序员通常不需要考虑底层硬件(例如CPU的数量或体系结构)来修改代码。对于内存分配来说,它们是相同的,其中许多CTM(低级语言编程接口)细节根据操作系统的体系结构(32/64位)而有所不同,CLR对其进行处理。

但是,在某些时候,每个应用程序都需要由处理器执行,这需要有机器码,这样汇编指令才能理解并且能够由CPU执行。根据操作系统或CPU的体系架构,有时需要在汇编代码中使用完全不同的CPU指令集。为了使源代码真正可移植,不能直接编译为机器代码,我们还需要使用中间语言。

中间语言(IL)

由于上述原因,托管运行时的编程语言源代码(如C#,F#或Java)不会直接编译为汇编语言。而是,它首先被编译为中间语言(IL)。CLR的中间语言也称为MSIL(Microsoft中间语言)。

将源代码编译为IL中是由特定的语言编译器执行的。这个过程是当你在Visual Studio构建应用时按F6或使用csc.exe进行编译时发生的。

源代码到IL的编译,由特定语言的编译器完成。例如,C#中由Roslyn编译,Roslyn是一个C#编译器,如图:

1.png

查看MSIL

为了查看已编译的EXE / DLL文件中包含的MSIL代码,你可以使用ILSpy Visual Studio扩展,安装这个扩展时,会在Visual Studio中添加一个菜单选项(tools – > ILSpy),你可以打开任何已编译的文件并查看包含对象的IL代码,如图:

2.png

实际上有哪些东西编译成IL代码呢?现在我们可以说,最重要东西就是methods(当然分为类,命名空间等)。还有许多其他的东西被编译(肯定比你预想的要多,通过查看源码你就知道了),但我们现在将重点关注methods。

好的,我们现在有了IL代码,但是CPU还是无法理解它。它是如何以及何时编译为汇编代码的?

JIT编译(即时编译)

丰田如何引入JIT?

我们先从一些历史背景开始说起。在20世纪60年代早期,由于仓储成本非常高,丰田的日本工程师不得不重新组织仓库管理。而且由于他们提前订购了大量零件,所以供应商交付也需要很长时间。每次交付都必须由人工去处理,因此他们需要很多员工。但是订购的零件会长期存放在仓库中,需要大量存储空间和并进行维护。

为了最大限度的降低成本,他们开创了即时制造(也称为丰田生产系统)。其主要原则是仅在仓库库存达到最低水平时才从供应商处订购货物。这样一来,货物是在生产线上正好需要它们的时间里交付的。它最大限度的减少了交付人员(和仓库员工)的数量,同时,也不像以前那样需要那么多的存储空间。

整个想法可以想象为以下流程顺序图:

3.jpg

JIT编译器

在丰田取得成功之后,CLR创建者在框架中引入了即时(JIT)编译器。它的作用是根据硬件和操作系统的特性(取决于对代码进行JIT编译的主机)将中间语言代码编译成汇编语言。与非托管语言不同,非托管语言在程序执行之前将源代码编译为机器语言,而IL默认情况下在运行时编译为CPU指令。这就是.NET工程师按时向CPU提供机器码的原理。

JIT编译器是CLR的一部分(类似于垃圾收集器)。简单来说,当某方法首次被调用时,JIT会编译该方法的源码。当然,它也会考虑到硬件,例如CPU类型及指令集来生成正确的汇编代码。然后,这个机器码就会保存在缓存中,并且在相同的应用程序运行,调用相同的方法时重新使用。这意味着在程序执行期间未调用的部分代码可能根本没有进行JIT编译。

JIT编译实际上是两种方法的结合:提前编译和解释,混合了两者的优点和缺点。但是,在运行时将IL代码编译为汇编语言时有几个特色功能,如动态类型(在程序执行时能够获取实际对象的类型)。这也是为什么JIT编译通常被视为动态编译的一种形式。

使用JIT编译的另一个优点是我们编写的源代码离硬件更远,因此代码可以编写的更具可读性和人性化,而与硬件交互的东西隐藏在IL中(不得不说,MSIL开发者确实厉害)

JIT编译也会有一些成本,在运行时编译代码需要时间。另一方面,不同的CPU单元具有不同的强大指令集或处理单元,这些指令集或处理单元会在由JIT生成的汇编代码中使用,而在提前编译中,则不是这种情况(没有任何特殊调整的情况下)。

我们还需要提前编译吗?

如果你真的不喜欢JIT编译(怎么会不喜欢?),有很多方法可以在应用程序执行之前预先编译你的EXE / DLL。这称为Pre-JIT编译。其中一个原因可能是许多用户将从相同的EXE / DLL文件运行我们的应用程序,JIT编译器通常会为每个用户对IL进行JIT编译。在这种情况下,出于性能和内存使用原因,使用预先JIT编译应用程序文件可能会更合适。可以使用NGen(适用于所有.NET版本)或.NET Native(适用于Windows 10的.NET应用程序)来完成。我们本文不会详细讲解这些工具(你可以在互联网上找到更多信息)。

总结

JIT编译是在运行时将中间语言(IL)编译为原生(机器)代码的过程。它提供了托管应用程序的可移植性。这是一个非常重要的概念,因为它已经在许多当前使用的编程框架中引入,如Java,.NET和Android。

最后,请记住:

4.jpg

华盛顿曾经说过,不要相信互联网上的一切!

本文翻译自:https://www.dsibinski.pl/2018/09/net-internals-09-just-in-time-jit-compilation/如若转载,请注明原文地址: http://www.4hou.com/system/13989.html
点赞 0
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论