(4)动态代码很慢吗?
动态编程语言易学易用,代码紧凑,开发灵活,但性能则一直是它的“软肋”。为了提升性能,DLR设计了一个三级缓存策略。
动态站点绑定对象会为动态调用表达式转换而成的语法树加上相应的测试条件(称为“test”),构成一个“规则(Rule)”,这个规则可以用于判断某个语法树是否可用于特定的动态调用表达式。
举个例子,请看以下这个动态表达式:
d1 + d2
如果在程序运行时d1和d2都是int类型的整数,则DLR生成的规则为:
if( d1 is int && d2 is int) //测试条件 return (int)d1+(int)d2; //语法树 |
DLR通过检查规则中的“测试条件”,就可以知道某个动态表达式是否可以使用此规则所包容的语法树。
“规则”是DLR缓存的主要对象。
前面介绍过的动态站点对象Target属性所引用的委托是第一级缓存,它实现的处理逻辑是这样的:
//当前处理规则,属于第1级缓存 if( d1 is int && d2 is int) //测试条件 return (int)d1+(int)d2; //满足测试条件,直接返回一个表达式树 //未命中,则在第2级、第3级缓存中查找,如果找到了,用找到的结果更新第1级缓存 return site.Update(site,d1,d2); |
如果3级缓存中都没有命中的规则,则此动态站点所关联的调用站点绑定对象会尝试创建一个新的规则。如果创建新规则失败,则由当前编程语言(比如C#)所提供的默认调用站点绑定对象决定如何处理,通常的作法是抛出一个异常。
当前版本的DLR第2级缓存了10条规则,第3级则缓存了100条规则。
由于DLR自身设计了一个“规则”缓存系统,又充分利用了CLR所提供的JIT缓存(因为所有动态调用代码最终都会转换为CLR可以执行的IL指令,而CLR可以缓存这些代码),使得动态代码仅仅在第一次执行时性能较差,后续的连续调用其性能可以逼近静态代码。
3 C# 4与动态语言的集成
由于几乎所有的编程语言都可以使用抽象语法树来表达,因此,在理论上DLR支持无限多种编程语言间的互操作,在当前版本中,可以实现C#/Visual Basic与IronPython和IronRuby的互操作,相信很快会出现其他动态编程语言的DLR实现。
一个有趣的地方是当前基于DLR实现的动态编程语言都以“Iron”开头,比如IronRuby和IronPython。IronPython的设计者、DLR的架构设计师Jim Hugunin曾经在微软PDC 2008大会上解释说主要是为了避免起一个“Python.NET”或“Python for .NET”之类“微软味十足”的名字,才有了“IronPython”。他强调:“Iron”系列动态语言将严格遵循动态语言自身的标准和规范,尊重这些动态语言已有的历史和积累,不会引入一些仅限于.NET平台的新语言特性,并且这些语言的.NET实现保持开源。与此同时,Jim Hugunin指出 “Iron”系列语言能很好地与.NET现有类库、编程语言和工具集成,并且能“嵌入”到.NET宿主程序中。
(1)动态对象通讯协议
由于各种动态编程语言之间的特性相差极大,实现各语言间的互操作是个难题。为此DLR采取了一个聪明的策略,它不去尝试设计一个“通用的类型系统”(CLR就是这么干的),而是设计了一个“通用的对象通讯协议”,规定所有需要互操作的动态对象必须实现IDynamicMetaObjectProvider接口,此接口定义了一个GetMetaObject()方法,接收一个语法树对象作为参数,向外界返回一个“动态元数据(DynamicMetaObject)”对象:
DynamicMetaObject GetMetaObject(Expression parameter); |
DynamicMetaObject对象向外界提供了两个重要属性:Restrictions引用一组测试条件,Expression属性则引用一个语法树。这两个属性组合起来就是可供动态站点对象缓存的“规则(Rule)”。
DLR中的“动态站点绑定对象(CallSiteBinder)”获取了DynamicMetaObject对象之后,它调用此对象所提供的各个方法创建“规则”,让“动态站点对象(CallSite<T>)”的Target属性引用它,完成动态绑定的工作。