以文本方式查看主题

-  中文XML论坛 - 专业的XML技术讨论区  (http://bbs.xml.org.cn/index.asp)
--  『 Dot NET,C#,ASP,VB 』  (http://bbs.xml.org.cn/list.asp?boardid=43)
----  也谈.Net中的效率问题  (http://bbs.xml.org.cn/dispbbs.asp?boardid=43&rootid=&id=11829)


--  作者:admin
--  发布时间:11/9/2004 2:26:00 AM

--  也谈.Net中的效率问题


发信人: flier (小海->找啊找工作 :)), 信区: DotNET        
标  题: 也谈.Net中的效率问题
发信站: BBS 水木清华站 (Mon Apr  8 02:26:01 2002)

也谈.Net中的效率问题(草稿)

Flier Lu

前面有朋友提到.Net中的效率问题,
我这里就我的一些了解结合他的关注点做一些介绍,
希望对大家提高对.Net的认识有帮助 :)

首先是.Net代码的执行效率。如果单单只是代码运行的话,
因为.Net是通过JIT编译成本地代码后执行,因此执行效率
并不比普通的VC/Delphi程序慢多少,比如MS做评测
C#的XML库实现效率比VC还要高,当然这是特殊情况。

而实际上很多朋友反映.Net程序运行很慢,的确如此,
为什么呢?如何避免呢?

因为.Net程序与普通程序不同,他首要的任务是保证代码的
稳定性,因此将类型检查、缓冲区溢出检查、数组越界检查、
数学运算溢出检查,加上GC、安全认证等等,效率不低才怪。
这些新特性在提高代码质量的同时是以运行时效率作为代价的。

如果你不需要这些特性,强烈建议你使用Unmanaged C++
将核心代码以传统形式开发编译执行,只是在对效率不敏感的
部分使用.Net来实现。这种方式更多的是需要了解两种C++
之间的不同与交互,我们以后有时间再详谈。

这里我关注的是如何提高必须使用.Net架构程序的运行时效率。
这方面需要关注的几点前面那位朋友其实基本上都提到了

1.自动内存管理,也就是GC
2.配件Assembly的装载
3.JIT编译的效率
4.各种运行时的检测
5.安全权限验证
6.各种小细节

因为这里不是写书,我只能各个点大概提一下,有兴趣的朋友
可以自行查阅相关资料了解详情。

1.自动内存管理,也就是GC

现代编程语言发展的一个重要趋势是使用自动内存管理,也就是GC

来摆脱依赖程序员人的因素的手工内存管理机制,从最开始的
智能指针,到基于缓冲池的内存管理,到GC,一步一步在发展。

GC的好处与坏处我这里就不多说了,大概介绍一下GC在.Net中的实现机制,
以及带来的对效率方面的影响好了,说不清的可以re此文和我讨论。

在.Net中,MS实际上是为GC做了相对多的效率上的优化的。

首先,在GC中,分配内存无需象传统C++/Delphi那样遍历一个可用内存链表,
而是有一个指针执行Managed Heap的使用到的末尾,直接通过指针+Size
来分配内存,效率达到最高。当然如果没有足够内存要引发强制性GC就ft了,
因此在进行注重效率部分之前,最好提前分配好需要用的对象、数组等等。
或者在之前使用GC.GetTotalMemory估算内存是否足够使用。
如果实在不够可以事先强制性GC.Collect()回收内存
总之,尽量避免在分配堆内存时引发GC。

注意:只有引用类型如类、数组才在堆里分配内存,值类型如结构、int等是
直接在堆栈里面分配空间。

其次,在GC中,所有实现了Object.Finalize()方法的类,在析构时需要
进行特殊处理。在C#里面使用类似C++析构函数的诸如 ~MyClass() 实现。
普通的对象GC一旦发现其不可到达,马上释放其内存;实现了Object.Finalize()
方法的对象,则被丢入一个列表,在第一遍GC完成之后逐个调用Finalize函数
再放回GC的队列中,在第三次时才被真正释放。

因此如果没有必要,绝对不要使用Object.Finalize()图方便,如果实在要
释放unmanaged resource,加一个Close或者Dispose方法好了,手工处理

此外如果频繁使用或者创建麻烦的对象,可以采用对象池的方法大大提高效率,
在其析构时放回对象池,使用时直接从池里拿,也可以大大提高效率。
过两天有时间我给个例子再说……

再就是GC的分代机制,把所有对象分为若干代Generation,
用GC.MaxGeneration可以取得最大代数,一般为3代,0-2
.Net的GC的实现思路是越晚建立的对象越可能被先释放
因此他将堆分为三代,每次缺省只处理第0代,所有不可访问对象回收,
可访问对象代数加一。这样就大大优化了GC时的工作量
我们也可以使用此机制,避免强制性的GC.Collect()回收全部堆
而一次只操作一代如GC.Collect(0);

再就是比较高级的优化手段弱引用,Weak References,
这里就不多说了,以后有时间再详谈

2.配件Assembly的装载

配件Assembly的装载也是影响效率的一大问题,特别是动态装载,
对效率影响更大。因为在装载配件时,CLR要分析配件的Metadata
要对不同配件之间进行关联,要检查配件的类型安全性,加上后面
要提到的进行静态安全验证等等,速度还是很慢的。

因此要减少动态的配件使用,尽量用静态连接,在程序开始时完成这些
麻烦事情。对实在要动态装载的配件,尽量把公共部分提取出来等等。
当然就算是静态连接,在载入配件时还是要花费相当时间,一个简单
的解决办法就是在程序开始对效率没有要求时,写一些无用的代码
去调用配件中等会要在注重效率代码中用到的类,强制CLR载入。
比如获取配件中类的类型信息等等

3.JIT编译的效率

JIT编译来说,是用到哪里编译到哪里,编译后代码在内存中缓存起来
因为一次编译代码不多,每次效率较高;同时也因为编译次数较多,
外加的负荷也较重。

不过解决办法也很简单,用ngen工具强制JIT预编译.Net程序的本地
代码镜像,并以文件形式缓存起来,这样既可以享受.Net程序的诸多优势,
又可以享受本地代码直接运行的高效率。

实际上.Net框架里面很多类都是被ngen预处理过的,保证其效率。
可以用ngen /show命令查看已经被预编译的配件。

4.各种运行时的检测

就JIT编译后代码来说,很大的影响效率的问题是各种运行时检测。
有些是不可避免的,如类型安全、数组越界;有些是可以选择的,
如数学运算溢出检查、缓冲区溢出检测等等,可以根据需要关闭之。
这种检测对计算密集型的程序打击最大,往往一个很简单的算法
里面被加进了一堆乱七八糟的东东。最简单的解决办法就是用
unmanaged c++来写,避开这些检测。

5.安全权限验证

安全权限验证对企业级应用来说至关重要,但其对效率的影响与其
功能强大成反比。比如你要检测一个方法调用者的权限,可能就需要
遍历整个stack list,检测每个调用者是否有相应权限,
整个过程耗时巨大。

解决办法一是尽量少用,只在必要的地方用。二是权限尽量限制窄。
三是尽量以静态验证(以attribute方式声明)代替动态验证
(以普通类形式使用),将大部分工作放到载入配件时

此外还可以使用一些高级的优化手段,如指定验证的范围,
缺省是堆栈列表中所有调用者,可以指定到一层,大大减少
消耗时间,但代价是安全性降低,可能出现安全漏洞
(程序A使用有权限的程序B来调用程序A没有权限访问的程序C)

6.各种小细节

算了,懒得写了,睡觉了…… :)

上面大概从较高的层面对.Net中效率问题进行了一些介绍,
以后有时间再写一篇从较低的代码层面的解决思路。

--
.  生命的意义在于   /\   ____\ /\_ \   /\_\                             .  
.      希望         \ \  \___/_\/\ \   \/_/__     __    _ _★           .  
.      工作          \ \   ____\\ \ \    /\  \  /'__`\ /\`'_\           .  
.    爱你的人         \ \  \___/ \ \ \___\ \  \/\  __//\ \ \/           .  
.   和你爱的人         \ \___\    \ \_____\ \__\ \____\ \ \_\           .  
.      ……             \/___/     \/_____/\/__/\/____/  \/_/ @126.com.  


※ 来源:·BBS 水木清华站 smth.org·[FROM: 61.183.136.8]
返回上一页
回到目录
回到页首
下一篇



W 3 C h i n a ( since 2003 ) 旗 下 站 点
苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
156.250ms