知秋一叶

日出而作,日入而息,凿井而饮,耕田而食,帝力何有于我哉
随笔 - 55, 评论 - 311, 引用 - 79

导航

工具

标签

每月存档

广告



访客

 

The need for Constraints
  C++将Generic Programming(泛型编程)的概念发挥得淋漓尽致。作为C++唯一的标准库,STL主要是应用Template,而不是面向对象的继承机制来实现的。很自然的,我们会把CLR和C++的Generic实现进行对比。Constraint是CLR在实现Generic时独有的概念,而且Constraint的使用也确实对CLR Generics在功能和性能方面有些负面影响。So, Why do we need constraints?
  CLR和C++在模板实例化采用了不同的策略:C++主要是编译期间实例化模板,也就是说当编译器看到程序中引用了vector<int>的时候,它就会用int代换vector<typename T>中的T,生成一份vector<int>的代码到目标文件,这样做有几个问题:
1) 代码冗余。如果在不同的.cpp文件中引用了vector<int>,那么每个编译产生的.obj都会包含vector<int>代码的一个拷贝。Link.exe必须能够正确处理这种特殊情况才能避免代码冗余。2) 由于实例化发生在编译期间,即使vector<int>仅在一个极少被调用的code path上被使用,它的实例化代码也会自始至终占据一块内存。3) 缺乏动态泛型支持(C++本身没有完整地Runtime的类型支持,并且不建议使用RTTI,所以这是和Generic一致的;但是如果向C#/Java这样本身支持Reflection的语言不支持Generics Reflection,就显得有些怪异了)。
  CLR选择了在Runtime动态实例化模板。编译器可以在没有泛型参数的情况下编译生成List<T>的扩展MSIL指令;在运行时刻,程序代码中第一次引用List<int>的时候,CLR才动态的使用int类型实例化List<T>,并且生成实例化的代码(实际上只有新的ValueType才会引发模板实例化,ReferenceType共享一个实例化的模板代码),这就解决了上面提到的两个问题。由于CLR完全理解Generic语义,就可以在Reflection当中添加Generic支持,第三个问题也迎刃而解。
  但CLR的实现方法也带来了新的问题:如果作为实例化参数的类不能满足Generic Type的要求(这些要求多种多样,可能是具有某个方法,某个操作符重载等等),实例化失败将会导致运行时错误,而不是像C++那样的编译连接错误。我们总是希望在编译期间检查出尽可能多的错误,但是显然不可能让C#编译器逐行检查Generic Type的MSIL指令来判断泛型参数是否符合要求(E.g. 如果Generic Type包含一条MSIL: call 0!.SomeMethod(),那么T必须定义SomeMethod)。折衷的方案是在Generic Type的MetaData里面显式的指明它对泛型参数的要求,这就是Constraint了。所以Constraint实际上是Generic Type对泛型参数要求的MetaData表示,其目的是帮助编译器尽早验证参数的合法性。
 
So, Constraint By Signature or By Type?
  Constraint在实现可以选择两种不同的语义,一是By Signature,C++实际上隐含了这种语义。例如:类型参数T必须定义一个返回bool,接受一个T类型参数的CompareTo函数;或者T必须重载operator+,等等。这种方式的优点很明显--灵活性大,我们可以自由表述对泛型参数的各种要求;它的缺点是需要非常复杂的Constraint语法(C++不需要显式指出Constraint)。另一种方式是By Type,指出泛型参数必须从某个类继承,或者实现某个接口,还是上面的例子,我们可以说T必须实现IComparable<T>这个接口。这是C#/.NET采用的方式,语法上看非常简单,和普通的类继承/接口实现非常相似;但是表达能力就不如By Signature的方法,像operator overload这些不能用接口描述的语义就很难表达了。
  Anders Hejlsberg在一篇专访当中谈到过C++和C#(实际上是CLR)在Generic实现上的异同,也分析了By Type Constraint的一些限制。


The Performance Implication of Constraint
  采用这种By Type的Constraint的确对Generic的性能有一些负面影响。例如:
    1) 由于Constraint必须采用interface,Value Type Object必须先Box,然后调用Constraint方法。如果采用By Signature,这个Box/Unbox操作应该可以避免。
    2) 我们分析过interface method call比direct method call 或者virtual method call要慢,采用Interface based Constraint即使对reference type也会造成interface call overhead。
  我在去年的MVP Summit上曾经问到过Constraint对Generics Performance的影响。我相信如果在JIT这个层次引入相应的Heuristic,上面的一些性能影响应该是可以被最小化的。当然,无论如何,采用Template + Constraint的List、HashTable始终要比使用Object的版本性能要优越些。

相关文章

Loading...

打印 | 张贴于 2004-04-13 10:40:00 | Tag:.NET

留言反馈

#回复: CLR Drilling Down: Generics, The need for Constraints 编辑
By signature性能上可能是很好的,但是我觉得这些约束太松散了,就算解决了Meta,还是有可能出现一些其他的问题。

此外,ValueTypeObject的Box/Unbox问题,我觉得是不是不一定啊?我记得没错的话,这个Contraint好像只是不能够约束到Object,没有说不能够约束到ValueType吧?也许可以这样来提高性能吧?
2004-04-14 11:27:00 | [匿名用户:sumtec]
#回复: CLR Drilling Down: Generics, The need for Constraints 编辑
完全同意 :)
2004-04-14 09:32:00 | [匿名用户:Justin Shen]
#回复: CLR Drilling Down: Generics, The need for Constraints 编辑
从Anders关于Generics的话来看,没有选择by signature的唯一原因是避免过于复杂的MetaData。因为Constraint信息会被保存到Type/Assembly的MetaData当中,过于松散的定义可能会导致数据格式很难处理。
I think now we are at the same square:
1) Constraint是必需的。
2) By Signature在更能和性能上要优于By Type。
3) 如果能克服上面我说的这一点,那么By Signature Constraint应该是更好的选择。:)
2004-04-13 23:55:00 | [匿名用户:qqchen]
#回复: CLR Drilling Down: Generics, The need for Constraints 编辑
由于C# Generics和C++ Generics在实现方式上的差异,一些在C++上非常有用的技巧,比如模板特化、元编程等,在C#上能实现吗?
2004-04-13 13:11:00 | [匿名用户:sam1111]
对不起,目前本随笔不允许发表新评论.

Powered by: Joycode MVC Blogger System