.Net Discovery系列之十一-深入理解平台机制与性能影响 (中)
上一篇文章中Aicken为大家介绍了.Net平台的垃圾回收机制与其对性能的影响,这一篇中将继续为大家介绍.Net平台的另一批黑马—JIT。
有关JIT的机制分析
● 机制分析
以C#为例,在C#代码运行前,一般会经过两次编译,第一阶段是C#代码向MSIL的编译,第二阶段是IL向本地代码的编译。第一阶段的编译成果是生成托管模块,第二阶段的编译成果是生成本地代码以供运行,从这里各位同学可以看出,第一阶段生成的MSIL是不能直接运行的。必须指出的是JIT在第一次编译IL后,会修改对应方法相应的内存地址入口,下一次需要执行这个方法时,CLR会直接访问对应的内存地址,而不会经过JIT了。
以Load()方法为例,假如Load()方法中调用了两次同类型中的方法:
Void Load()
{
A.a1("First");
A.a1("Second");
}
static class A
{
Public void a1(string str){}
Public void a2(string str){}
Public void a3(string str){}
}
运行时,操作系统会根据托管模块中各种头信息,装载相应的运行时框架,Load()被加载,由于是第一次加载,这会触发对Load()的即时编译,JIT会检测Load()中引用的所有类型,并结合元数据遍历这些类型中定义的所有方法实现,并用一个特殊的HashTable(仅用于理解)储存这些类型方法与其对应的入口地址(在未被JIT前,这个入口地址为一个预编译代理(PreJitStub),这个代理负责触发JIT编译),根据这些地址,就可以找到对应的方法实现。
在初始化时,HashTable中各个方法指向的并不是对应的内存入口地址,而是一个JIT预编译代理,这个函数负责将方法编译为本地代码。注意,这里JIT还没有进行编译,只是建立了方法表!
图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来证实以上的说法,加载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() { }
}
}
使用name2ee命令遍历所有已加载模块,如下图:
回车后注意高亮区域的信息:
高亮区域显示的是“”,这说明虽然运行和程序,但未点击按钮时,A类型未被JIT,因为它还没有入口地址。这一点体现了即时、按需编译的思想。
同样,!name2ee *!JITTester.B和!name2ee *!JITTester.C命令会得到同样的结果。
好,现在继续,Detach Debuggee进程,并回到程序中点击“GO”按钮
然后重新附加进程,这时程序已经调用了new A().a1()方法,并重新执行令!name2ee *!JITTester.A ,注意高亮部分
和图4中的信息比较,图6中的方法表地址已经变为JIT后的内存地址,这时图2中的Stub槽将被一条强制跳转语句替换,跳转目标与该地址有关。这一点说明JIT在大多情况下,只编译一次代码。
同样命令查看B类型:
该类型未被调用,所以还未被JIT。
C类型:
由于实例化A类型时和C类型相关,所以C类型已经JIT了。
这就是一个类型被JIT的全部过程。
● 性能影响分析
通过以上的分析,大家已经能够了解,即时编译这个过程是在运行时发生的,这会不会对性能产生影响呢?事实上答案是虽然是肯定的,但这种开销物有所值,并且如上所说的,JIT在第一次编译IL后,会修改对应方法相应的内存地址入口(绕口啊~~),下一次需要执行这个方法时,CLR会直接访问对应的内存地址,而不会经过JIT了。
1.JIT所造成的性能开销并不显著。
2.JIT遵循计算机体系理论中两个经典理论:局部性原理与8020原则。局部性原理指出,程序总是趋向于使用最近使用过的数据和指令,这包括空间的和时间的,将局部性原理引申可以得出,程序总是趋向于使用最近使用过的数据和指令,以及这些正在使用的数据和指令临近的数据和指令(凭印象写的,但不曲解原意);而8020原则指出,系统大多数时间总是花费80%的时间去执行那20%的代码。
根据这两个原则,JIT在运行时会实时的向前、后优化代码,这样的工作只有在运行时才可以做到。
3.JIT只编译需要的那一段代码,而不是全部,这样节约了不必要的内存开销。
4.JIT会根据运行时环境,即时的优化IL代码,即同样的IL代码运行在不同CPU上,JIT编译出的本地代码是不同的,这些不同代码面向自己的CPU做出了优化。
5.JIT会对代码的运行情况进行检测,并对那些特殊的代码经行重新编译,在运行过程中不断优化。
此外你可以利用NGen.exe创建托管程序集的本机映像,运行该程序集时,就会自动使用该本机映像而不是JIT它们。这听起来似乎很美妙,但是你必须做好以下准备:
1.当FrameWork版本、CPU类型、操作系统版本发生变化时,.Net会恢复JIT机制。
2.NGen.exe工具并不能避免发布IL,事实上,即使使用NGen.exe工具,CLR依然会使用到元数据和IL。
3.忽略了局部性原理(上一节中提到的),系统会加载整个映像文件到内存中,并很可能重定位文件,修正内存地址引用。
4.NGen.exe生成的代码无法在运行时进行优化,无法直接访问静态资源,也无法在应用程序域之间共享程序集。
所以,除非你已十分清楚程序性能是由于首次编译造成的性能问题,否则尽量不要人工生成本地代码。
JIT很优秀,它不但有编译的本事,还会根据内存资源情况换出使用率低的代码,节省资源,这对于一些基于.Net平台的电子产品是很重要的。基于B/S模式运行的系统,如果使用率较高,可以基本忽略JIT带来的性能损失,因为根据局部性原理与8020原则,常用的模块都是编译完毕的,只有那些不常用的模块,在第一次使用时会被编译,并损失用一些时间。
未完
转自:http://www.cnblogs.com/isline/archive/2010/04/07/1705966.html
.Net Discovery系列之十一-深入理解平台机制与性能影响 (中)的更多相关文章
- .Net Discovery系列之十-深入理解平台机制与性能影响(上)
转眼间<.Net Discovery>系列文章已经推出1年了,本文为该系列的第10-13篇文章,在本文中将对以前所讲的.Net平台知识做一个小小的总结与机制分析,引出并重点介绍这些机制对程 ...
- .Net Discovery系列之十二-深入理解平台机制与性能影响(下)
上一篇文章中Aicken为大家介绍了.Net平台的垃圾回收机制.即时编译机制与其对性能的影响,这一篇中将继续为大家介绍.Net平台的异常捕获机制与字符串驻留机制. 三.关于异常捕获机制 虽然我们已经很 ...
- .Net Discovery 系列之五--深入浅出.Net实时编译机制(上)
欢迎阅读“.Net Discovery 系列”文章,本文将分上.下两部分为大家讲解.Net JIT方面的知识,敬请雅正. JIT(Just In Time简称JIT)是.Net边运行边编译的一种机制, ...
- .Net Discovery 系列之六--深入浅出.Net实时编译机制(下)
接上文 在初始化时,HashTable中各个方法指向的并不是对应的内存入口地址,而是一个JIT预编译代理,这个函数负责将方法编译为本地代码.注意,这里JIT还没有进行编译,只是建立了方法表! 下表(表 ...
- Java基础系列2:深入理解String类
Java基础系列2:深入理解String类 String是Java中最为常用的数据类型之一,也是面试中比较常被问到的基础知识点,本篇就聊聊Java中的String.主要包括如下的五个内容: Strin ...
- .Net Discovery 系列之七--深入理解.Net垃圾收集机制(拾贝篇)
关于.Net垃圾收集器(Garbage Collection),Aicken已经在“.Net Discovery 系列”文章中有2篇的涉及,这一篇文章是对上2篇文章的补充,关于“.Net Discov ...
- 【圣诞特献】Web 前端开发精华文章推荐【系列二十一】
<Web 前端开发精华文章推荐>2013年第九期(总第二十一期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各种增强网站用户体验的 jQuery 插件,展示前沿的 HTML5 和 ...
- 单元测试系列之十一:Jmockit之mock特性详解
本文是Jmockit学习过程中,根据官网所列的工具特性进行解读. 1.调用次数约束(Invocation count constraints) 可以通过调用计数约束来指定预期和/或允许匹配给定期望的调 ...
- CRL快速开发框架系列教程十一(大数据分库分表解决方案)
本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...
随机推荐
- 关于内核中spinlock的一些个人理解 【转】
由于2.6内核可以抢占,应该在驱动程序中使用 preempt_disable() 和 preempt_enable(),从而保护代码段不被抢占(禁止 IRQ 同时也就隐式地禁止了抢占).preempt ...
- mysql主从复制跳过复制错误【转】
跳过复制错误 mysql因为binlog机制问题,有些时候会出现从库重放sql执行失败的情况,特别是旧的STATEMENT模式最容易出现这种情况(因为函数和存储过程等原因),这也是为什么强调使用mix ...
- eclipse安装阿里巴巴java开发规范插件
阿里巴巴java开发规范插件 作为JAVA开发人员,始终没有一个明确的规范,何为好代码,何为坏代码,造成不同人的代码风格不同,接手别人代码后改造起来相当困难.前不久,阿里巴巴发布了<阿里巴巴Ja ...
- Flask源码解析:Flask应用执行流程及原理
WSGI WSGI:全称是Web Server Gateway Interface,WSGI不是服务器,python模块,框架,API或者任何软件,只是一种规范,描述服务器端如何与web应用程序通信的 ...
- 001_shell经典案例
一. 二. -n, --numeric-sort compare according to string numerical value -k, --key=KEYDEF sort via a key ...
- 钉钉机器人-实现监控通知功能-python
1. 首先得创建有 一个 钉钉群.(因为只能发群通知) 2. 添加机器人,得到一个url: 3. 开始写Python脚本: # -*- coding: utf-8 -*- ""&q ...
- java Comparator和Comparable(比较器)
Comparable: 一个类实现了Camparable接口则表明这个类的对象之间是可以相互比较的,这个类对象组成的集合就可以直接使用sort方法排序,sort方法调用compareTo()方法里定义 ...
- MySQL的lock tables和unlock tables的用法(转载)
早就听说lock tables和unlock tables这两个命令,从字面也大体知道,前者的作用是锁定表,后者的作用是解除锁定.但是具体如何用,怎么用,不太清楚.今天详细研究了下,总算搞明白了2者的 ...
- 003.SMB相关文件
一 常用文件 /etc/samba/smb.conf #主配置文件 /etc/samba/lmhosts #对应NetBIOS名与主机IP的文件,samba会自动搜索(只对本机生效) /etc/sam ...
- jqplot利用ajax传值画图表(利用jsp连接数据库)
实现从数据库中取得数据,再把数据传给画jqplot的jsp界面!jsp界面再进行画图,画出我们所需的图表! 有两个jsp界面,7-12.jsp用来连接数据库,并把数据传给ajaxauto.jsp,aj ...