(转载)Metadata是.NET平台的核心灵魂
July 7th, 2010 jzli Leave a comment Go to comments
网友来信:李老师,您好!我参加过你去年到我们公司做的.NET深度培训,也拜读过你的译作:《.NET框架程序设计(修订版)》和 《Effective C#》,受益匪浅,非常佩服你这样优秀的.NET技术专家。前几天在博客园上的C#大论战,不知道您看过吗?特别是其中一个网友firelong所写的几篇轰动的帖子,对.NET的性能提 出了许多批评。这个话题在我们项目组(大多数都参加过你去年的培训)也引起了很多争论,很想听听李老师对这些观点的看法?……….
本来是以email的形式回复这位朋友的,但是写着写着发现写得长了,最后考虑将其以博文的形式登出。
其实个人无意参与这样的论战,因为很多口水帖、情绪贴. 我简单浏览了一下,发现论战的各方(firelong,jeffreyzhao,以及.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支持者还是反对者),抛掉 “非要辩个胜负,分个高低”的怪诞氛围,而是来一些扎扎实实的技术说理过程,相信会更有意义——如是,则国内技术社区成长可待!