.net framework的核心是黑盒复用

http://www.cnblogs.com/topic/53/

 

http://www.cnblogs.com/firelong/archive/2010/07/09/1774490.html

 

 

关于近期C#大论战的回应

 

自从在cnblogs和csdn写了几篇批评C#/.NET的博文后,便受到了多方.NET粉丝的轮番群殴:http://www.cnblogs.com/topic/53/。这段时间正好出差,没有及时回复,便被某些朋友视作理屈词穷。其实,我在第一篇博文中就说过,我既然列出这些论点,一定有支持这些论点的实践证据和技术原因——也许有些观点错误,但是我总有我的道理。说出来,和大家分享讨论而已——没有及时回帖,只是工作原因,绝不会理屈词穷,请大家放心,我还会继续战斗下去,呵呵:)

 

我仔细看了目前为止参与“群殴”的博文,说实话,大部分都是没有技术含量、错误百出的。比如如下观点:《说透 事件和接口,两者怎能互相替代》:http://www.cnblogs.com/waitrabbit/archive/2010/07/02/1769948.html 博文中直接说“事件是特殊的委托”? 就这水平,还能“说透?”。先搞清楚什么叫“委托”,什么叫“接口”,什么叫“事件”吧! 再比如:《完全水文系列之0xFA:函数背后的臃肿设计哲学》:http://www.cnblogs.com/winter-cn/archive/2010/07/05/1771098.html果然是水的厉害,自己都承认水了。我就不回应了。

 

不过,可喜的是,在众多水贴之中,确实还出来了几篇高质量的帖子,这是让我非常高兴的,也算是对我写系列博文的一个回报吧。目前看到的主要有以下几个帖子我列举如下,并且加上我的回应(如果有朋友认为自己的帖子质量很高,技术水平很深,我遗漏掉了,请在下面回帖,我会认真拜读并回应),与大家一起讨论、共勉:


1.  李建忠《Metadata是.NET平台的核心灵魂》http://techvery.com/jzli/2010/07/07/metadata是-net平台的核心灵魂/

 

这篇博文是目前我看到技术分析最深入的一篇博文,让我很受益。李建忠是我尊敬的一位.NET技术专家,我承认学习.NET就是从拜读李建忠翻译的《.NET框架程序设计》(Jeffrey Richter著,后改名为CLR via C#)一书开始,我也参加过李先生公司去年举办的.NET技术大会,才有前文提到的和Jeffrey Richter大牛的对话。

 

李建忠在这篇博文中批判了我之前博文中的一个论据“.NET平台中metadata的目的是为了支持反射”。我必须坦白地承认,我之前确实一直认为“metadata就是为了支持反射才创建的。” 经过李建忠博文的分析,我承认在这个问题上认识不到位,观点偏颇。并且非常同意李建忠博文的观点“.NET使用metadata的主要目的是实现组件的高度复用性”。

 

李建忠博文中还有很多很有价值的技术分析,比如COM的缺点,.NET为什么使用metadata?metadata都在.NET中有哪些具体的应用?特别是“基于逻辑语义的组件复用”和“基于物理实现细节的组件复用”的对比,受益匪浅。

 

拜读过这篇文章之后,我也承认“基于逻辑语义的组件复用”对于软件开发平台非常重要,不过却有一点要和李建忠商榷,难道“性能就不重要了吗?”,“.NET为了基于逻辑语义的组件复用,就可以大幅度地牺牲软件的性能吗?”,非常希望听到李建忠和网友的继续分析。

 

 2. mikelij《关于.net反射和metadata加载--致Jeffray Zhao等几位和firelong》http://www.cnblogs.com/mikelij/archive/2010/07/04/1770897.html

 

mikelij这篇博文涉及到两个观点:

a. 我在前面分析反射时的观点:程序(EXE/DLL)最后都是要加载到内存中运行的,不是光放在硬盘上的——这也是为什么.NET程序占用内存都超多。

b. Jeffray Zhao在前文中反驳我的观点是 "不JIT的话,是不会把整个dll加载到内存中的,而是用多少加载多少,这点已经讨论了很多遍了"

 

mikelij这篇博文非常细致的分析和数据说理,以及其后面的网友跟帖讨论已经清楚表明了,我在这个问题上的观点是正确的。不过后面有很多跟帖一直在说“任务管理器里面的内存占用很小,所以说明老赵的观点是正确的,即调用哪个方法,就加载哪个方法和它的元数据。用不到的方法,不加载”。

 

这个里面触及到了一个较深入的话题,即Windows操作系统的内存加载规则和Windows任务管理器的数字含义。 我对这个问题的回应如下:

 

Windows任务管理器显示的内存大小指的是“工作集working set所使用的内存大小”,也就是当前运行期加载到CPU缓存里面的内存大小。并不是程序运行期间所有加载到内存中的数据。这些数据有可能停留在主存,或者虚拟内存即硬盘上——这就是性能成本,因为这很快会因为调用而遇到page fault, cache missing的问题。只是因为它们当前不被调用,所以没有放在working set缓存中,但是并不表明它们不被加载到主存或者虚拟内存中。

 

而程序在运行期间,EXE/DLL整个程序集是整体被加载,而不是部分加载——不会因为你只用一个程序集中的方法A,就只加载方法A。而是会将程序集中其他类、方法、元数据都加载进去:mikelij这篇博文充分证明了这一点。

 

JIT编译是按照方法为单位——即用哪个方法,编译哪个方法。但是加载是按照程序集为单位,不是以方法为单位——老赵在这个问题上的认识让人失望。

 

谢谢mikelij这篇数据翔实的博文!


3. 老赵《我支持托管运行时(虚拟机)http://blog.zhaojie.me/2010/07/why-i-support-managed-runtime-virtual-machine-based-program.html

 

实话实话老赵对.NET的很多分析还是有益的,至少让我可以深入再思考一些问题,虽然老赵经常犯一些幼稚的错误(比如前面的将程序集的加载问题,混淆成了JIT问题)。 这篇文章中,老赵说了好几个虚拟机的优点,我整体比较同意,比如实现跨语言,“跨执行环境”等。

 

但是说虚拟机完全可以实现比native代码更高的性能,则有点过于他信.NET了。特别是举出了一个虚函数的内联优化的天方夜谭的神话,一个虚函数即便循环调用,你也绝对无法去预先判断下一次的调用地址——如果可以判断,这个判断的成本,要远远高于内联所节省的性能成本(而且是每一次都要判断!)。老赵这个真的实现了吗?能不能给出参考地址?

 

虚拟机可以实现的优化,native代码都可以,而且优化力度要大得多。但是很多native可以做的优化,虚拟机就不行。虚拟机,顾名思义要经过一层间接,我无论如何都想象不出来,间接层可以提升性能?希望老赵能够深入分析一下原理或者给出案例。

 

 

============================

相关文章:

 

Metadata是.NET平台的核心灵魂

 

网友来信:李老师,您好!我参加过你去年到我们公司做的.NET深度培训,也拜读过你的译作:《.NET框架程序设计(修订版)》和《Effective C#》,受益匪浅,非常佩服你这样优秀的.NET技术专家。前几天在博客园上的C#大论战,不知道您看过吗?特别是其中一个网友firelong所写的几篇轰动的帖子,对.NET的性能提出了许多批评。这个话题在我们项目组(大多数都参加过你去年的培训)也引起了很多争论,很想听听李老师对这些观点的看法?……….

本来是以email的形式回复这位朋友的,但是写着写着发现写得长了,最后考虑将其以博文的形式登出。

其实个人无意参与这样的论战,因为很多口水帖、情绪贴. 我简单浏览了一下,发现论战的各方(firelongjeffreyzhao,以及.NET社区众网友),最后都情绪激动了,情绪激动之下讲的话大多都丢失了技术最扎实、最基本的原理。但是这些文章在口水之外,确实有一些深层次的技术问题暴露出来——有些甚至跨越.NET,而延伸到更广泛的领域。但是,它们却被口水淹没了,没有好好深入讨论下去,非常可惜。这便是我最后决定写这篇文章的原因。我希望能够对其中被忽视的一些技术问题,做一些有意义的探讨,与大家共享。

首先我想就《C#会重蹈覆辙吗?系列之2:反射及元数据的性能问题》一文中的主要技术点来谈谈。这篇文章主要观点如下(并且给了相应的分析):
1. 使用反射会有很高的性能成本
2. 即使不用反射,为支持反射而产生的metadata也有很高的性能成本

因此,作者才得出来在《C与C++社区混战,C#会重蹈覆辙吗?》一文中“删除反射”的结论。关于反射和metadata带来的性能问题,我想没有人会否认,只是其性能问题到底有多糟?我后面有空的话也许会另文讨论(firelong文章中有部分含糊其辞)。

本文中,我想讨论的是《C#会重蹈覆辙吗?系列之2:反射及元数据的性能问题》一文的整个推理过程中的一个基本论点:.NET平台中metadata的目的是为了支持反射。

在这个论点的基础上,文中才有“反射并不常用,为了支持反射所创建的metadata却带来很多性能问题。因此删除反射,也就不需要创建metadata,这样.NET就不会为此再付出性能损失(就像C、C++那样)”。

很不幸,这个基本论点是对.NET平台的极大误解。换言之,在.NET平台中metadata的存在绝对不仅仅只是支持反射。实际上,支持反射只是metadata一个很小的用武之地,metadata是整个.NET平台非常核心的基础支撑设施,它在.NET平台中有着广泛的应用,是.NET平台的灵魂。

为什么这么讲?这要从.NET创建时的整个软件时代背景来谈起,我们才能深刻理解这一点。.NET创建之前,Windows平台的软件技术经历了以下几个阶段:DDE、OLE、ActiveX/COM。这些技术所努力解决的核心目标是:软件组件的复用性——这是软件开发的核心命题,是软件平台厂商竞逐的焦点。“软件组件的复用性”有以下几个含义:

1. 强调二进制级的复用(黑盒复用),而不是源代码级的复用(白盒复用)。
例如:我想在我的软件中集成PDF阅读器的能力,我不需要找Adobe要PDF阅读器的源代码,而是去找Adobe要一个支持PDF阅读的二进制组件——然后通过接口的方式来使用它。
2. 强调多语言创建的组件之间的复用,而不是单一语言创建的组件之间的复用。
例如:我在C++中写一个Email收发组件,可以在VB中直接使用。

DDE、OLE、ActiveX/COM无一不是围绕这个目标。COM是这些技术发展进程中的一个高峰。但是COM技术本身在发展过程中暴露出了很多问题。Don Box在《Essential .NET》一书的第一章“The CLR as a Better COM”对COM的问题有非常深刻的解剖。

首先,要达致“组件复用”这一目标,必须有合同来规范组件与执行环境、组件与组件之间的各种约定。COM使用的是IDL或TLB作为组件合同,但它们有几个根深蒂固的缺点:

1. IDL/TLB规范的是物理语义(例如v-table偏移,栈帧,参数,数据结构,对齐等内存细节),且使用的是C++语言的一个子集约定。
2. IDL/TLB规范描述与组件代码本身分离。
3. IDL/TLB规范较为混乱,没有达成业界统一的标准(第三方难以扩展开发)

其中第3点问题是微软对COM没有前后一致的系统规划导致——这一点如果假以时日是可以解决的。但是前面1、2点的问题是COM根深蒂固的问题,导致了COM组件后期出现的各种难以解决的问题——不同语言实现面向COM的编译很难,因为满足了COM的复用模型,往往要打破该语言本身模型所约定的复用。同时在一个语言中维护两套编译模型、和复用机制,步履维艰(在C++、VB、Delphi等语言中开发COM组件的痛苦相信很多人深有体会)。

而这个时候,95-98年间名声大噪的Java给了微软相当大的启发。特别是其中的metadata,微软的技术精英发现metadata是最好的组件合同定义体。Metadata有以下几个非常好的特点(这些正好克服了COM组件描述IDL/TLB的缺点):

1. Metadata描述的是逻辑结构,不涉及任何物理细节(例如v-table偏移,栈帧,参数,数据结构,对齐,方法地址等物理细节,直到在具体平台上加载、执行时,才被确定,比如IL代码中经常看到的CALL指令后面的 MyClass:MyMethod就是逻辑上的元数据,而不是具体方法的物理地址)
2. Metadata本身与组件代码合并在一个文件中(即程序集),包含了组件依赖,版本等信息
3. Metadata从一开始就经过系统、精心的设计,是CLI业界标准的一部分(便于第三方开发相关应用)

这些特点使得metadata非常适合作为.NET组件与执行环境(CLR)、组件与组件之间(不用语言开发的组件)理想的合同规范。特别是第1点,“Metadata虚拟的逻辑属性”使得组件合同专注于“逻辑语义层面”,而非“物理实现细节”。简单解释一下:
1. 组件与组件的复用从此集中于“逻辑语义层面”,比如C1的方法M1中调用MyClass的方法MyMethod,用语义表达为:call MyClass:MyMethod。或者C1类继承C2类:用语义表达为:class C1 extends C2。或者调用虚方法:callvirt MyClass:VirtualMethod….
2. 组件与组件的复用不再纠缠于“物理实现细节”,例如:调用某个方法,必须知道它的入口点地址如jmp 0×000688;继承某个类,必须清楚它的字段layout等等,调用虚方法,必须清楚v-table偏移规定…..

那为什么“专注于逻辑语义层面的合同”要优于“专注于物理实现细节的合同”呢?

这就又要回到前面说的“软件组件的复用性”这一目标上来。简单来说,如果要实现“软件组件的复用性(注意二进制、多语言两个属性)”,“专注于物理实现细节”对于各个语言的编译器厂商,要求太高了,各编译器厂商要协调一个执行体各个方面的物理细节(v-table偏移,栈帧,参数,数据结构,对齐,方法地址等等…..不一而足)——这么多细节的要求,使得大家极难协调——COM后期的弊端丛生就源于此。

而“专注于逻辑语义层面”来实现组件的复用性,各编译器厂商生成的执行体(即.NET里面的程序集)只需要“用metadata表达逻辑语义上的复用规范”即可——这对于各编译器厂商需要协调的规范的量级大大减少,实现起来相对容易得多。也不容易在各种细节上出现漏洞——因为大量的物理细节全由CLR执行时来确定。

综上所述,metadata的根本性的作用是支持“基于逻辑语义的组件复用”——这是.NET致力于打造软件开发平台的核心目标。支持反射仅仅是metadata一个很小的衍生功能,而非主要功能。

事实上除了支持“基于逻辑语义的组件复用”这一核心目标外,metadata在.NET平台中还起着其他重要作用(有些是组件复用的延伸需求,比如JIT与跨平台):

1. Metadata是JIT编译、亦即实现.NET跨平台的基础
说明:IL代码中大量引用着Metadata。在MethodDef元数据表里面存储着一个方法的各种语义信息,许多IL指令就直接内嵌了Metadata Tokens。没有Metadata,JIT也无法实现。

2. Metadata是.NET支持垃圾收集GC的基础
说明:metadata标记了对象与对象间的引用关系,这是GC遍历对象图(判断对象是否可以收集)的关键依据。没有metadata,GC将不知道0×000688是一个指针(需要继续遍历)?还是一个整数(不需要继续遍历)?

3. Metadata是描述类型契约、标识、规范等的基本信息
说明:强命名程序集(使用metadata描述)唯一标识了编译器当初编译时的组件,不至于导致运行期仿冒、或者版本偷换(即DLL 地狱问题)

4. Metadata是.NET管理组件安全的基础(运行时类型检查,组件下载)
说明:metadata会告诉CLR执行环境一个组件的安全边界,从而可以管理那些恶意代码。

5. Metadata是.NET管理组件引用关系,加载的基础
说明:metadata会告诉一个组件需要引用哪些组件(哪些版本)?这是组件加载的基础。

从这里大家可以看出来,Metadata是.NET平台实实在在的基石。Metadata之于.NET平台,就像维他命至于生物体一样。换言之,删除反射,并不能消除metadata。如果要消除metadata,.NET平台的整个设计基础(组件复用,以及上述的其他种种功能)将不复存在。

最后,指出《C#会重蹈覆辙吗?系列之2:反射及元数据的性能问题》一文中的核心论据错误,并非为了追求驳倒firelong。建议firelong,jeffreyzhao以及这场辩论的众多技术领域的网友(不管是firelong,jeffreyzhao支持者还是反对者),抛掉“非要辩个胜负,分个高低”的怪诞氛围,而是来一些扎扎实实的技术说理过程,相信会更有意义——如是,则国内技术社区成长可待!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值