当前位置: 学习中心 -> 入门基础 -> 文章详细
 
IronPython 和动态语言运行时
3/10/2008
Microsoft® .NET Framework 旨在于单个运行时"公共语言运行时 (CLR)"上支持各种各样的编程语言。CLR 向这些语言提供共享服务,包括垃圾收集、实时 (JIT) 编译、沙箱安全模型以及对工具集成的支持。对语言实现者来说,共享这些功能有两大好处。实现一种语言更容易了,因为许多底层的设计工作已经完成。另一个好处是,运行于 CLR 上的多种语言可以无缝地集成。通过共享库和框架,对 CLR 陌生的语言可以构建在其他语言的工作之上。

CLR 还提供对动态语言的支持,IronPython 1.0 即是明证 (www.codeplex.com/ironpython)。程序员对动态编程语言的感受是功能强大、生产高效,James Schementi 在 2006 年 10 月期的“CLR 完全介绍”(msdn.microsoft.com/msdnmag/issues/06/10/CLRInsideOut) 中对此有具体介绍。现在,Microsoft 正在构建动态语言运行时 (DLR),向 CLR 中添加了一组专门用于满足动态语言需要的服务。DLR 添加的功能包括:一个共享的动态类型系统,一个标准的承载模型,以及对简化生成快速动态代码和快速符号表的支持。凭借这些附加功能,可以十分轻松地为 .NET 构建高质量的动态语言实现。此外,这些功能使得构建于 DLR 之上的动态语言可以共享用其他动态语言或基于 CLR 的静态语言编写的库。

DLR 可让您获得使用自己最喜爱的动态语言的最佳体验。这就是说,用您自己的语言编写代码,这种愉悦的感受一如既往。您还能从出色的工具、优良的性能和共享库中受益。因为使用 DLR 即意味着可以共享标准功能,所以您有更多时间来关注语言本身所具有的独特功能。例如,您完全不必再去实现垃圾收集、代码生成和方法缓存功能。

DLR 使用自适应的方法缓存来产生快速动态程序,DLR 上的所有语言实现均从这种共享的工作中受益。由于避免了重复搜索类优先顺序列表,所以,代码运行得更快了。它还避免了每次调用对象时解析 .NET 方法上的重载(比如,调用某个对象 o 上的方法 foo)。

IronPython 和 DLR 的源代码可遵照 Berkeley Software Distribution (BSD) 形式的 Microsoft Permissive License 从 Codeplex 上获得(参见 2.0 Alpha 版)。这是一个很不成熟的版本,还有大量的工作要做。底层机制尚需设计,性能增强尚需实现,文档资料尚需编写。当您阅读本专栏时,有多个 Codeplex 项目尚在实施过程中,包括 DLR 代码本身、IronRuby 以及 IronPython。虽然我们正在进行 DLR JScript® 和 VBX 实现,却也只能提供二进制的形式。


IronPython Hello World

我将详细讲解一个简单的 Python 程序(稍加变化的 Hello World),并讨论它以 ipy.exe 执行时发生了什么。我要讲解的程序是 msdnmag.py:

def yo(yourname):
text = "hello, "
return text + yourname

print yo("bill")

我在传统的“print hello”功能之上添加了一点东西,这样我们就可以探讨 IronPython 实现中更加有趣的一些方面。例如,我将能够演示标识符引用的名称绑定和在 yo 函数中隐藏“+”的含义。

我使用的是 IronPython 2.0 alpha,所以,现在就花几分钟下载,然后您就可以和我一起往下进行。

首先,在 Visual Studio® 2005 中打开 ironpython.sln 文件,用 Debug 解决方案配置构建解决方案。然后右键单击 IronPythonConsole 项目,在左侧导航栏中选择“调试”选项卡。将此项目设为启动项目(“启动操作”下方的第一组单选按钮)。接下来,在“启动选项”下方输入 msdnmag.py 作为命令行参数。最后,将上面列出的 msdnmag.py Python 代码复制到 \bin\debug\ 目录下名为 msdnmag.py 的文件中,该文件是您构建解决方案时创建的。


启动 Ipy.exe

现在,让我们看一下 ipy.exe 是如何启动的。DLR 提供了一些默认的控制台类,从而使语言实现者可以轻松地运行解释器(实现者仍需要为他的语言编写分析器和运行时实现)。控制台类处理输入和输出、命令行开关和 DLR 实现的某些共享的诊断开关。这样就减轻了语言实现者在 DLR 上运行一些初始代码的工作,而且它还提供了一个测试工具。

首先,打开 <installdir>\Src\IronPython\Compiler\Generation\PythonScriptCompiler.cs。然后在 ParseFile 函数的“using (Parser parser = ....”行上设置一个断点

按 F5(“调试”|“启动调试”)运行程序。当解析对 Python 执行引擎进行初始化的代码段时,您将首先遇到该断点。该代码段加载 Python site.py file 文件,以便进行计算机特定的初始化。那么,您是如何到达此处的呢?如果看一下调用堆栈,就会发现 ipy.exe 在 PythonConsoleHost 的 Main 函数中启动,Main 函数调用共享的 DLR 便利代码。执行过程回到 IronPython 特定代码,以处理命令行,这就导致了 msdnmag.py 文件的运行。

PythonConsoleHost 类很小。它有一个非常简短的 Initialize 方法,该方法将 LanguageProvider(就此成为执行引擎)设置为 IronPython 实现。PythonConsoleHost 的 Main 方法只是调用默认的 ConsoleHost 类,该类由 DLR 提供。

ConsoleHost 是一个帮助器类,语言实现者可用它迅速构建一个 cmd.exe 样式的交互式外壳程序。ConsoleHost 类从 LanguageProvider 获得 CommandLine 帮助器对象。ConsoleHost 运行 CommandLine 对象,调用 CommandLine 对象的 Initialize 方法,此时,代码开始在 PythonCommandLine 对象中执行。

PythonCommandLine 对象在其 Initialize 方法中设置用于加载模块的 Python 路径,设置某些内置模块,并导入 IronPython 的 site.py 文件。这些全都是标准的 Python 行为。PythonCommandLine 通过在代码段的“import site.py”字符串上调用 IScriptEngine.Execute 来导入 site.py 文件。

您在调用 PythonScriptCompiler.ParseFile 的过程中首先将遇到断点,这是由于解析“import site.py”而引发的,不过,让我们继续往下执行,因为 msdnmag.py 还没有运行。


AST 简介

当 ipy.exe 执行 msdnmag.py 时,IronPython 实现与 DLR 协作,编译该文件中的代码。在基础层面上,编译器是以分析器为起点的管道,分析器产生一种表示代码的数据结构。此数据结构按照编译器的说法是一种抽象语法树 (abstract syntax tree),或者叫做 AST。编译器在代码分析阶段对 AST 进行转换,或者创建新的数据结构。然后,编译器根据由此得到的结构生成机器码或中间语言 (IL) 代码,后者用于虚拟机,如 CLR。

IronPython 代码首先生成一种特定于 IronPython 的 AST,然后将此树映射到 DLR AST。某些语言有它们自己的中间树,从而它们可以执行分析,或支持用以处理其 AST 的工具(代码编辑器)。这些工具需要一个与用户在编辑器中键入的源代码接近的树。许多语言可能都具有与 DLR AST 类似的 AST,不过,DLR AST 将具有更多关于显式表达的语义的信息。您可以将 DLR AST 看作众多转换形式中的一种,编译器在后期阶段将需要这些转换。

有趣的是,DLR 只是在代码运行时才懒洋洋地编译部分 AST。这与 CLR 的编译方法类似,CLR 仅在代码运行时才使用 JIT 编译。代码第一次运行时会牺牲一些性能,但后续执行速度很快。稍后您会看到,AST 片段直到执行特定的代码行时才会进行编译。


查看 IronPython AST

让我们回过来再运行程序,查看一下 IronPython AST。首先,按 F5(“测试”|“继续”),再次在此断点处停止,此时,IronPython 引擎开始解析 site.py 的内容,以便执行该文件。如果您查看 Visual Studio“局部变量”窗口,可以依次展开“cc”,“SourceUnit”,然后查看 Name 或 DisplayName,看看 ParseFile 正在处理什么内容。

再次按 F5。我们最后一次在此断点处停止。如果您现在查看“局部变量”,就会看到 cc.SourceUnit.DisplayName 是 msdnmag.py。这是我向 ipy.exe 中添加的作为命令行参数的文件。在“局部变量”窗口中,cc.SourceUnit.Name 是“__main__”,因为 Python 就是用这种方式命名主执行模块的。

现在,按 F10(“调试”|“单步跳过”)三次,跳过下面一行

ast = parser.ParseFileInput()

现在,您可以在调试器中查看 IronPython AST,如图 1 所示。

图 1 调试器中的 IronPython AST
图 1 调试器中的 IronPython AST

您可以看到,AST 根节点是一个代表一系列语句的 SuiteStatement,并有一个 Statements 成员。展开第一个语句(索引零),您会看到它有一个 FunctionDefinition 节点。其中的 Body 成员是一个表示函数定义语句的 SuiteStatement,其中的 Name 成员是将要绑定到由此产生的函数对象的名称。

用省略某些细节的图形表示,整个 msdnmag.py 文件的 IronPython AST 看起来与图 2 类似。

图 2 msdnmag.py 的 IronPython 抽象语法树
图 2 msdnmag.py 的 IronPython 抽象语法树  (单击该图像获得较大视图)

在树中混合使用了可视化的表现形式。在绝大多数情况下,如果一个 AST 节点有一个指向另一个 AST 节点的成员,图中就显示一个指向子节点的箭头。通常,子节点以父节点中成员的类型命名。例如,FunctionDefinition 节点有一个 SuiteStatement 类型的成员,那么,下一个节点就是一个 SuiteStatement。有时,某个节点包含一个对子节点的描述,描述采用的是缩进和花括号的形式。我选择用该方式显示这些节点是为了简化图表。


查看 DLR AST

IronPython 现在已做好构建 DLR AST 的准备,以便编译的最后阶段可以运行。在 ParseFile 中的断点下面,您可以看到对 BindAndTransform 的调用,其功能是将 IronPython AST 转换为 DLR 树。在该过程中,IronPython 解析所有的标识符,从而使它们在 DLR 树中的表示方式指向它们所代表的变量的声明/分配信息。当然,对于某些标识符来说,它们需要编译为一个代表在运行时对值进行搜索的 AST 节点;例如,标识符可以解析为一个运行时添加的模块成员,或者解析为一个由主机通过模块目录提供的后期绑定的值。

在此部分中有许多细节,也许只有您想将一种语言移植到 DLR 时才有必要了解它们。如果您只是查看 DLR 树的图示,请注意其中的 MethodCallExpression 节点和 ActionExpression 节点。MethodCallExpression 节点编译为对执行一个操作的 IronPython 运行时函数的调用。每当调用此函数时,它都采用传递给它的参数完成相同的工作。

ActionExpression 节点特别有趣,因为它代表一个抽象的操作或动作。例如,抽象动作包括提取成员、索引对象、调用对象、在对象上执行添加等。DLR 将 ActionExpression 节点编译为动态调用点。动态点 (Dynamic sites) 是一种运行时机制,它缓存适合特定动作和参数类型的组合的方法。这种机制使动态代码运行得更快,在编译时只有很少的静态类型信息或完全没有静态类型信息。稍后我会详细解释动态点。

让我们回过来一步步地执行程序。按 Shift+F11(“调试”|“跳出”)两次,停在 SourceUnit.Compile 中,然后按 F10(“单步跳过”),执行对变量“block”的赋值,该变量将持有整个文件的 DLR AST 的根。

现在我们可以查看 DLR AST。在 Visual Studio“局部变量”窗口中,展开名为 block 的节点,它是一个 CodeBlock AST 节点。这是 Python 代码文件的根节点。将某一语言的特定语法树转化为 DLR AST 的一部分工作就是明确地指明了声明变量(显式或隐式)的位置和标识符所指向的变量。如果您展开 block 的变量成员,就会看到两个在模块作用域内声明的变量,即“__name__”和“yo”。(展开元素 0 和元素 1,查看 Variable 对象的名称成员。) 第一个变量是 Python 需要的,并为模块创建的绑定名称。第二个变量是持有我们的函数定义的模块变量。

在深入探讨主体之前,快速查看一下图 3 中对 DLR AST 的部分扩展,该图反映了与您早先看到的 IronPython AST 类似的表现方式:

图 3 DLR AST
图 3 DLR AST  (单击该图像获得较大视图)

展开 block 下方的 Body 属性。鉴于 Visual Studio 调试器的显示特性,您需要展开 body 节点下的第一个节点才能真正看到 BlockStatement 的内容。此 DLR BlockStatement AST 节点对应于 IronPython AST 的 SuiteStatement 节点,各自都代表一系列语句。展开语句属性。

第一个语句(索引 0)是一个 EmptyStatement,它是 IronPython 转化到 DLR 树的产物。如果存在一个模块文档字符串,IronPython 就会分配两个语句,这种情况下会导致创建对 __doc__ 的赋值。此示例中,由于不存在任何文档字符串,第一个语句留作占位符 EmptyStatement。

第二个语句(索引 1)是我们的兴趣所在,因为它是文件(即我编写的代码)其余部分的 BlockStatement。此 BlockStatement 包含一个 ExpressionStatement,其后跟随一个 BlockStatement(稍后讨论)。ExpressionStatement 节点封装了一个 BoundAssignment 节点,从而使赋值的结果值从堆栈中弹出;Python 赋值不返回值。BoundAssignment 具有名称和值成员。它们表示将局部变量 yo(局部是相对于 Python 代码执行所在的模块而言)设置为一个函数调用的结果。函数调用由 MethodCallExpression 表示,它调用创建 IronPython 函数对象的运行时帮助器函数。MethodCallExpression 有一些成员表明了调用对象是名为 MakeFunction 的方法以及调用所采用的参数。一个参数是用于该函数主体的 CodeBlockExpression AST 节点,另一个参数是一个参数数组,其中包含所创建的函数将要采用的参数。

展开 MethodCallExpression AST 节点,它是 BoundAssignment 的值成员。看一下 Arguments 属性的第三个元素(索引 2),您会看到 yo 的主体的 CodeBlockExpression。它有一个 Block 属性,该属性是一个 CodeBlock。CodeBlockExpression 使它所封装的 CodeBlock 转变成可调用的值,该值可以分配给模块变量。在我们具体讨论 yo 函数的 AST 的主体之前,先看一下图 4,这是 msdnmag.py 的完整 DLR AST 图示。

图 4 msdnmag.py 的完整 DLR AST
图 4 msdnmag.py 的完整 DLR AST  (单击该图像获得较大视图)

图中有几个地方需要注意。与前面的图相似,在树中混合使用了可视化的表现形式。在绝大多数情况下,如果一个 AST 节点有一个指向另一个 AST 节点的成员,图中就显示一个指向子节点的箭头。有时,某个节点包含对子节点的描述,描述采用的是缩进和花括号的形式,而不是更多的箭头和方框。在图的左侧,Visual Studio 中“局部变量”窗口的快照显示了 AST,往下一直到 MethodCallExpression 节点。第一个箭头是虚线的,因为该图略去了带有 EmptyExpression 的 BlockStatement。您可以在“局部变量”窗口中进一步展开数据结构,查看图中所示的树的完整结构。

代表 yo 函数主体的 CodeBlock 节点具有变量成员、参数成员和主体 AST 成员。这类似于代表整个 msdnmag.py 文件的 CodeBlock。稍后您会看到,该文件的 CodeBlock 将转变为该模块的一个 Initialize 方法。如果您展开变量成员,展开元素 0,然后展开 Variable 对象,就会看到,yo 函数有一个局部变量 text。通过类似的展开操作,您还会看到,yo 函数有一个参数 yourname。

CodeBlock 的主体是 BlockStatement,它有两个语句:一个赋值语句和一个返回语句。就像您在上面看到的对 yo 的赋值,BlockStatement 首先有一个持有 BoundAssignment 的 ExpressionStatement。此赋值表示将 text 设置为“hello,”。BoundAssignment 对象有一个 ConstantExpression 值成员和一个 Variable 成员。Variable 对象将 text 描述为一个局部变量,而且有一个指回 CodeBlock(该局部变量作用域)的 block 成员。

BlockStatement 中的第二个语句是 ReturnStatement,其表达式成员是 ActionExpression。就像我上文解释的那样,此 AST 节点特别有趣。此 ActionExpression 节点有一个 DoOperationAction 动作成员,在本示例中它代表“添加”操作。ActionExpression 有一个参数成员,带有的两个元素都是 BoundExpression 对象。第一个 BoundExpression 有一个变量成员,代表局部变量 text,其值设为“hello,”。第二个 BoundExpression 有一个变量成员,代表参数 yourname。

现在,让我们转回到树的上方,看看包含两个 ExpressionStatments 的 BlockStatement。一个用于将 yo 的 BoundAssignment 绑定到一个函数对象。还存在第二个 ExpressionStatement,以便编译器生成代码,将其表达式的值弹出堆栈。ExpressionStatement 有一个 MethodCallExpression 表达式成员。

MethodCallExpression 代表对实现 print 语句的 IronPython 运行时函数的调用。它有一个方法成员,是描述打印的 .NET RuntimeMethodInfo。MethodCallExpression 有一个 ActionExpression 参数成员。正如上面讨论的那样,这编译为用于快速调用的动态点。ActionExpression 有一个动作成员,是调用 yo 的 CallAction。此节点还有一个带有两个元素的参数成员。一个是 BoundExpression,它有一个变量成员,表示该表达值是局部变量 yo 的值(yo 对于定义它所在的模块而言为局部变量)。另一个参数是 ConstantExpression,它有一个持有字符串“bill”的值成员。

我们对 DLR AST 的详细探讨就此结束。您可以看到 IronPython 树如何直接代表您所看到的 Python 代码。DLR AST 中有更多明确信息。它包含标识符绑定信息和表征作用域的显式代码块。它包含对运行时方法调用(调用 Print)的显式表述,而不是编译为动态点(“text + yourname”的节点)的动作。DLR AST 还包含促使表达式结果从堆栈中弹出的节点,从而使代码遵循 Python 语义。


查看生成的代码

既然您已了解进程如何启动和执行分析的过程,让我们来看一下生成的代码。最简便的方法是使用 ipy.exe 的开关和 Lutz Roeder 的 Reflector 程序。这些开关可致使 ipy.exe 生成静态代码(与轻量级代码生成器或动态方法相对)并保存到磁盘。从 aisto.com/roeder/dotnet/ 下载 Reflector,然后在您构建 ironpython.sln 所在的 bin debug 目录中调用下列命令行:

ipy.exe -D -X:SaveAssemblies -X:StaticMethods msdnmag.py

随后您会在该目录中发现两个文件:snippets1.dll 和 msdnmag.exe。现在,使用下列命令行对这些文件启动 Reflector(根据需要填写绝对路径):

C:\where\you\put\reflector.exe snippets1.dll, msdnmag.py

您可以向下翻阅 DLL 和 EXE 列表,查找 msdn 和 snippets1。依次展开 msdnmag、msdnmag.exe、“{} -”,最后到“__main__$mod_2”。现在双击 Initialize 方法。这是 msdnmag.py 文件的主体。您应能看到从 DLR 生成的 IL 中分解出来的源代码。如果您以前用过 Reflector,就会知道它是一个很棒的工具,但它也有一些缺陷。例如,它会额外生成一些临时变量,所以您需要对所看到的 C# 代码持保留态度。

我不会详述 Initialize 函数,但您可以看到,yo 被设置为调用 MakeFunction 的结果,这与您在 AST 中看到的相同。您可以看到,Initialize 随后针对调用 yo 产生的结果,调用运行时帮助器 PythonOps.Print。请注意片段“Call-Simple-1.Invoke”。Call-Simple-1 对象是方法缓存机制的一部分。我会通过它在 yo 函数中的具体表现进一步说明方法缓存机制。

我们来看一下 yo 函数。双击左窗格中的 yo$1。您可以看到它在哪里将 text 设为“hello”,以及它在哪里返回“DoOperation-Add-0.Invoke(text, yourname)”的结果。那里就是我用 Python 编写的代码的本质。我马上会重点说明 DoOperation-Add-0,它就是缓存方法搜寻结果的对象。在深入探讨 DoOperation-Add-0 的过程中,我们还将查看 snippets1 中的一些运行时生成的函数。


理解动态点

动态点可使动态语言代码快速运行。它们管理对特定类型的对象执行特定操作的方法缓存。DLR 使用的动态点机制建立在对经历时间验证的动态语言实现的研究和试验的基础之上。您可以看到生成的代码中的对象,如 Call-Simple-1 和 DoOperation-Add-0,它们是从 DynamicSite<Targ1, Targ2, Tresult> 中派生的类型的实例。我在这里将简要介绍缓存理论,不过,您可以从网上搜索“dynamic language method caching”或“polymorphic inline method caching”,以获得详细信息。

动态语言的性能障碍在于每个调用点上发生的额外检查和搜索。您每次执行一行特定的代码时,直接的实现必须重复搜索成员的类优先顺序列表,并有可能对方法参数类型的重载进行解析。在 o.m(x, y) 或 x + y 之类的表达式中,动态语言需要检查对象 o 的确切类型,o 上所绑定的 m 是什么,x 是什么类型,y 是什么类型,对于 x 和 y 的运行时实际类型,“+”的含义是什么。在静态类型语言中(或代码中具有足够的类型暗示和类型推断),您可以确切地发出每个调用点上适用的指令或运行时函数调用。您可以做到这一点,因为从静态类型中可以得知编译时需要什么。

动态语言因其动态功能,提供了极大的生产效能增强和功能强大的简练表达式。不过,在实际工作中代码倾向于每次都在同一类型的对象上执行。这意味着您可以在一段代码首次执行时记住方法搜索的结果,从而提高性能。例如,就 x + y 而言,如果表达式首次执行时 x 和 y 都是整数,就可以记住一段代码序列或执行两个整数相加功能的确切的运行时函数。此后,每当该表达式执行时,就不会再涉及搜索。代码只是再次检查 x 和 y 是否为整数,并分派给正确的代码,而无需任何搜索。结果实际上可缩减为含有两次类型检查和一个加法指令的内嵌代码生成,这取决于操作的语义和所采用的方法缓存机制。


DynamicSite 对象工作原理

静态语言编译器可以在编译时选择方法或生成特定代码,以执行相加、成员提取和编制索引等操作。由于 DLR 在编译时无法即时得知要生成什么代码,它就生成在 DynamicSite 对象上调用 Invoke 方法的代码。此对象捕获该操作和一个包含缓存逻辑的委托(通过 Invoke 调用)。在运行时,每当该委托发现一个新的参数类型组合时,此缓存逻辑就会得到更新。(注意,这些委托实际上封装了指向函数的指针,不过为简便起见,我使用委托来充当函数。) 这只是简单叙述;接下来是更详细的说明。

委托首先在其内部发起一个对 UpdateBindingAndInvoke 的调用。调用点首次执行时(让我们只考虑 x + y),UpdateBindingAndInvoke 查询一个“加法”操作实现的参数,以获得 x 的类型和 y 的类型。如果它获得了参数的类型,就会生成一个新委托,该委托会编制一个检查 x 和 y 的代码,检查它们是否具有您刚才所见的相同类型。在此后的调用中,如果 x 和 y 具有相同的类型,则新委托的代码就会调用我们所得到的实现。如果 x 或 y 在后期调用中具有不同的类型,该委托就不能实现所有的缓存检查,并再次调用 UpdateBindingAndInvoke,生成一个新委托。最新委托的代码将捕获第一次对 x 和 y 的测试以及新测试,调用与类型匹配的目标实现。

现在我们来看一下 DoOperation-Add-0 委托,它是 DLR 从运行 msdnmag.py 中的代码生成的。转回到 Reflector,这一次在左窗格中展开 snippets1、snippets.dll、“{} -”和“Type$_stub_$4”节点。单击 Handle 方法,在右窗格中您会看到图 5 中的代码。

这是从中间语言中分解出来的代码,该中间语言是 DLR 在执行“text + yourname”后为 DoOperation-Add-0 委托生成的。如果再次调用 yo 函数,此委托就会执行。您可能认识到,此代码有一些性能问题,但我们稍后再讨论该问题。您可以看到,代码会测试两个参数是否为字符串,并调用 IronPython 运行时实现,执行两个给定字符串相加功能。

如果其中一个参数不是字符串,那么委托就会调用 UpdateBindingAndInvoke,以生成新的委托。新委托将取代当前委托(即上述委托),因此,下次 DoOperation-Add-0.Invoke 运行时,就会调用更新后的委托。我更改了 msdnmag.py 代码,从而使第二次调用 yo 时“text + yourname”在两个整数上执行。随后我发现了 DLR 在第二次调用后生成的第二个委托。它看起来与图 6 类似。

我 清除了 Reflector 插入的临时假变量。如果您做我完成的这个额外实验,您会得到稍微不同的代码。现在您会看到,该代码会测试参数是否为整数。如果它们是整数,则调用实现整数 “加法”的运行时帮助器函数。我以前编写的相同代码跟随在整数测试和目标代码之后。我选择的测试顺序是先测试最新类型。通过简要介绍程序的运行,您可以想 像按最佳顺序生成所有适当的参数测试。当然,如果参数有一种前所未见的新类型组合,委托就会转而求助于调用 UpdateBindingAndInvoke。

您可能注意到了代码的性能问题。DLR 如今可能生成更好的代码,不过,与任何 alpha 版本一样,DLR 对功能和代码生成有所取舍。您可能注意到,代码对一种类型(字符串)进行测试,然后将参数转换为字符串。这就存在两个问题。第一个问题是,.NET Framework 2.0 JIT 不会将“if”测试中的类型信息传播到后继的代码块中。由于“加法”实现是一种静态类型方法,那么,它的参数需要显现为字符串。第二个问题是,DLR 生成的代码执行一个对运行时转换函数的缓慢调用。DLR 在此处可能已发出了一个转换操作指令。

需要说明的最后一个性能问题是,DLR 如今并不为避免重复相同的测试而对测试进行构造。更新后的委托知道如何快速执行两个整数或两个字符串的相加,现在我用空值对参数进行测试。当我不一定要全面进行各种测试时,我多次进行这种空值测试。当 UpdateBindingAndInvoke 搜索操作实现时,它实际上询问对象如何执行操作。它请求对象提供一个如何执行由 DynamicSite 代表的操作的规则。该规则包含一个用于执行测试的 AST 和一个用于测试为真的情况下执行目标代码的 AST。DLR 可以在测试过程中执行树转换,以更进一步地优化委托的代码。

最后一点,因为我使用了动态点提取对象的成员,我必须处理对实例和动态类型的更改。DLR 无法对 o.m() 做出假定:o 如果指向同一个对象,它便确切地知道该调用哪一个 m。某些语言允许类型在运行时发生更改,并且它们可以替换成员,实际上在一个实例中覆盖一个成员。DLR 使用一个全局版本计数器,对新类型而言增加数字,对原有类型而言可更改数字。导致类型版本发生变化有多方面的原因,比如更改成员。对于任何指向对象的 DynamicSite 缓存,如果对象的类型版本发生变化,则缓存就会失败。此时,DynamicSite 就会重新搜索并更新委托。


重要的回报

生成的 DynamicSite 类型是通用类型。在 msdnmag.py 的 Python 示例代码中,使用通用类型没有多大用处。不过,在诸如“if x < 1:”这样的表达式中,您可以生成一个 DynamicSite<Object, Int, Bool>。该点的 Invoke 方法将采用静态类型,采用一个整数参数并返回一个布尔值。动态点内部的委托也以这种方式采用静态类型,从而使产生的代码速度更快。该委托只须在其缓存中测试一个参数,当堆栈弹出时,JIT 便知道一个布尔值被释放到正确的位置。这就消除了与 if 测试有关的任何转换或检查。

某些动态语言具有可选的显式类型定义。即使它们没有显式类型定义,实现也可以推断出类型,推断的依据包括对 .NET 静态方法的调用、文字常量等。它们可以通过变量的生存期、条件语句的分支等传播此类型信息。当语言具有类型信息时,它们可以生成 DLR AST,进而编译为具有完整类型信息的 DynamicSite 类型。这样就可导致在缓存中生成 Invoke 方法调用和目标代码调用的内嵌代码。

如今,DLR 生成的通用 DynamicSite 类型具有六个或更少的通用类型参数。第一个 N-1 参数代表参数的类型,而 Nth 代表操作结果的类型。在六个类型之后,必须生成仅执行对运行时帮助器函数的方法调用的 AST。这些函数必须延迟工作,以便在运行时完成全部计算。将来,DLR 可让您使用通用聚合,以提供大量的采用静态类型的参数。当我们得出绝大多数程序的最佳结合点后,我们也可能将该参数增加到六个以上。

对于如何查看特定于语言的 AST 和 DLR AST,您现在应该有了相当明确的概念。凭借简单的树上的一些基本信息,您可以检查某些语言在 DLR 上构建的方式。如果您试图在 DLR 上实现自己的语言,则可以使用此类技巧。即便您并不想实现自己的语言,只是查看一下深层次的东西也是一件有趣的事。

您还看到了 DLR 向语言实现者提供的一些共享的基础结构。从 CLR 和 DLR 身上可以利用许多东西"垃圾收集、JIT 编译、动态点、公共动态类型模型,等等。在 .NET 运行时上实现语言现在可产生相当好的结果,而且只会变得更好。第 1 版的 DLR 将会相当棒。用不了多久,在 .NET 上实现动态语言将会非常轻松,产生的结果将是出类拔萃的。

相关链接和资源


上一篇: Visual Studio 2008学习中心
下一篇: VC++ 2008扩展包发布
 
     
 
联系我们 | 保留所有权利 | 商标 | 隐私声明
 COPYRIGHT(C)2005 Microsoft Corporation ALL RIGHTS RESERVED.
京ICP备05084263号