.Net Discovery 系列之六--深入浅出.Net实时编译机制(下)

简介:

    接上文

    在初始化时,HashTable中各个方法指向的并不是对应的内存入口地址,而是一个JIT预编译代理,这个函数负责将方法编译为本地代码。注意,这里JIT还没有进行编译,只是建立了方法表

    下表(表1)为首次加载调用时HashTable的情况:

                     表1 方法表示意

方法槽

方法描述

a1()

PreJitStub

a2()

PreJitStub

a3()

PreJitStub

    好了有了这个HashTable后,JIT开始编译第一个被调用的方法A.a1("First"),这是由一个JIT内部函数来完成的(上面提到的),遗憾的事,目前还没有发现介绍这个函数的相关资料,有些书中称它为“JIT编译者”,那本文也这么称呼它吧。

    下图为首次调用方法时的示意图:

 

 

图2 触发JIT编译

    JIT借助元数据和IL生成被调用方法的本地代码后,会将这些代码缓存在动态内存中,然后修改HashTable中对应方法的入口地址,将其修改为本地代码的内存片地址(如表2所示),并将这个地址返回给CLR经行执行,A.a1("First")执行完毕,代码继续运行。

    运行至A.a1("Second ")时,会直接执行A.a1()方法的内存代码,不会进行再次编译,表2 为再次加载时HashTable的情况。

              表2 方法表变化

方法槽

方法描述

a1()

XXXXXXXXX内存地址

a2()

PreJitStub

a3()

PreJitStub

    再次加载流程示意图:

 

图3 未触发JIT编译

 

 

图4 方法表、方法描述、预编译代理关系

 

    图2中所示的MS核心引擎指的是一个叫做MSCorEE的DLL,即Microsoft .NET Runtime Execution Engine,它是一个桥接DLL,连同mscorwks.dll主要完成以下工作:

  1. 查找程序集中包含的对应类型清单,并调用元数据遍历出包含的方法。
  2. 结合元数据获得这个方法的IL。
  3. 分配内存。
  4. 编译IL为本地代码,并保存在第3步所分配的内存中。
  5. 将类型表(就是指上文中提到的HashTable)中方法地址修改为第3步所分配的内存地址。
  6. 跳转至本地代码中执行。

    所以随着程序的运行时间增加,越来越多的方法的IL被编译为本地代码,JIT的调用次数也会不断减少。

      下面借助WinDbg来证实以上的说法,示例中的源程序可以到这里下载到:

      http://files.cnblogs.com/isline/IsLine.JITTester.rar

 

复制代码

  
  
namespace JITTester

{

public partial class
Form1 : Form

{

public
Form1()

{

InitializeComponent();

}



private void Form1_Load(object
sender, EventArgs e)

{



}



private void GO_Click(object
sender, EventArgs e)

{

new
A().a1();

lb_msg.Text
= "调用完毕!"
;



}

}



class
A

{

public void
a1() { }

public C a2 = new
C();

}



class
B

{

public void
b1() { }

public void
b2() { }

}



class
C

{

public void
c1() { }

public void
c2() { }

}

}

复制代码

 

      代码中定义了3个类,分别为A、B、C,在“GO”按钮按下后,将调用类型A中的a1()方法,而Form1_Load 中什么也不做,目的是程序运行后,在空载的情况下查看方法描述对应地址入口的情况。

    好,第一步运行JITTester.exe程序,并打开WinDbg附加这个进程

 

图 5 附件进程

 

   第二步,附加进程成功后,在WinDbg中加载SOS.dll

 

图6 加载SOS.dll

    第三步,使用name2ee命令遍历所有已加载模块,name2ee格式为name2ee *! [程序集].[类型]

 

图7 查看类型信息

    回车后注意高亮区域的信息:

 

图8 JIT前A类型的信息

    高亮区域显示的是“<not loaded yet>”,这说明虽然运行和程序,但未点击按钮时,A类型未被JIT,因为它还没有入口地址。这一点体现了即时、按需编译的思想。

   同样,!name2ee *!JITTester.B和!name2ee *!JITTester.C命令会得到同样的结果。

    好,现在做第4步操作,Detach Debuggee进程,并回到程序中点击“GO”按钮

 

 

图9 点击按钮

 

    第五步 重新附加进程(参考第一步),这时程序已经调用了new A().a1()方法,并重新执行命令!name2ee *!JITTester.A ,注意高亮部分

 

图10 JIT后A类型的信息

    和图8中的信息比较,图10中的方法表地址已经变为JIT后的内存地址,这时图4中的Stub槽将被一条强制跳转语句替换,跳转目标与该地址有关。这一点说明JIT在大多情况下,只编译一次代码。

    同样命令查看B类型:

 

图11 JIT后B类型的信息

    该类型未被调用,所以还未被JIT。

    C类型:

 

图12 JIT后C类型的信息

 

    由于实例化A类型时和C类型相关,所以C类型已经JIT了。

    第三节.Native Image Generator

    Native Image Generator中文译为本地代码生成器,我更习惯叫它“本地映像”,因为通过工具NGen.exe生成的本地代码是无法部分载入的,这意味着操作系统会加载整个程序集文件。

    上一节中提到过,有两种方法可以获得本地代码,JIT方式和Native Image Generator方式,JIT方式是在运行时动态编译需要的代码,而NGen.exe会创建托管程序集的本机映像,并且将该映像安装到GAC中,运行该程序集时,就会自动使用该本机映像而不是JIT它们。

这听起来似乎很美妙,但是你必须做好以下准备:

  1. 当FrameWork版本、CPU类型、操作系统版本发生变化时,.Net会恢复JIT机制。
  2. NGen.exe工具并不能避免发布IL,事实上,即使使用NGen.exe工具,CLR依然会使用到元数据和IL。 
  3. 忽略了局部性原理(上一节中提到的),系统会加载整个映像文件到内存中,并很可能重定位文件,修正内存地址引用。 
  4. NGen.exe生成的代码无法在运行时进行优化,无法直接访问静态资源,也无法在应用程序域之间共享程序集。 

    此外,JIT不但有编译的本事,还会根据内存资源情况换出使用率低的代码,节省资源,这对于一些基于.Net平台的电子产品是很重要的。 

    所以,除非你已十分清楚程序性能是由于首次编译造成的性能问题,否则尽量不要人工生成本地代码。 


本文转自Aicken(李鸣)博客园博客,原文链接:http://www.cnblogs.com/isline/archive/2009/12/27/1633453.html,如需转载请自行联系原作者

相关文章
|
3月前
|
Android开发
解决Android、Flutter编译时Gradle报错:javax.net.ssl.SSLException: Connection reset
解决Android、Flutter编译时Gradle报错:javax.net.ssl.SSLException: Connection reset
328 0
|
4月前
|
机器学习/深度学习 计算机视觉 网络架构
【YOLOv8改进 - 注意力机制】HCF-Net 之 PPA:并行化注意力设计 | 小目标
YOLO目标检测专栏介绍了HCF-Net,一种用于红外小目标检测的深度学习模型,它通过PPA、DASI和MDCR模块提升性能。PPA利用多分支特征提取和注意力机制,DASI实现自适应特征融合,MDCR通过多层深度可分离卷积细化空间特征。HCF-Net在SIRST数据集上表现出色,超越其他方法。论文和代码分别在[arxiv.org](https://arxiv.org/pdf/2403.10778)和[github.com/zhengshuchen/HCFNet](https://github.com/zhengshuchen/HCFNet)上。YOLOv8的PPA类展示了整合注意力机制的结构
|
6月前
|
小程序 安全 JavaScript
.NET微信网页开发之通过UnionID机制解决多应用用户帐号统一问题
.NET微信网页开发之通过UnionID机制解决多应用用户帐号统一问题
.NET微信网页开发之通过UnionID机制解决多应用用户帐号统一问题
|
安全 网络协议 JavaScript
Open-Dis的C++版本编译(CMake-gpu 3.21.4)以及SDL2和SDL_net库的配置使用
Open-Dis的C++版本编译(CMake-gpu 3.21.4)以及SDL2和SDL_net库的配置使用
346 0
Open-Dis的C++版本编译(CMake-gpu 3.21.4)以及SDL2和SDL_net库的配置使用
从openjdk.java.net获取OpenJDK8源码并编译(amd64/aarch64/arm64)
从openjdk.java.net获取OpenJDK8源码并编译(amd64/aarch64/arm64)
340 0
|
Linux
LINUX下载编译:segment.jar/net.loomchild.segment.srx.Srx2SaxParser
LINUX下载编译:segment.jar/net.loomchild.segment.srx.Srx2SaxParser
68 0
|
安全 JavaScript API
.NET微信网页开发之通过UnionID机制,解决用户在不同公众号,或在公众号、移动应用之间帐号统一问题
.NET微信网页开发之通过UnionID机制,解决用户在不同公众号,或在公众号、移动应用之间帐号统一问题
238 0
.NET微信网页开发之通过UnionID机制,解决用户在不同公众号,或在公众号、移动应用之间帐号统一问题
|
存储 开发框架 Java
【CLR C#】浅谈.Net的GC(垃圾回收)机制及其整体流程
在.NET程序开发中,为了将开发人员从繁琐的内存管理中解脱出来,将更多的精力花费在业务逻辑上,CLR提供了自动执行垃圾回收的机制来进行内存管理,开发人员甚至感觉不到这一过程的存在。.NET程序可以找出某个时间点上哪些已分配的内存空间没有被程序使用,并自动释放它们。自动找出并释放不再使用的内存空间机制,就称为垃圾回收机制。本文主要介绍.Net中的GC(垃圾回收)机制及其整体流程。
【CLR C#】浅谈.Net的GC(垃圾回收)机制及其整体流程
|
开发框架 .NET 应用服务中间件
|
开发框架 .NET
ASP.NET Core 3.x Razor视图运行时刷新实时编译
ASP.NET Core 3.x Razor视图运行时刷新实时编译
128 0