在为动态类型的语言编写编译器时与类型相关的挑战是什么?


9

本次演讲中,Guido van Rossum正在谈论(27:30)有关尝试为Python代码编写编译器的评论,并评论说:

事实证明,编写一个可以维护所有出色的动态键入属性并保持程序语义正确性的编译器并非易事,因此无论您在幕后的某个地方进行何种操作并实际运行,它实际上都可以执行相同的操作任何更快

在为像Python这样的动态类型语言编写编译器时,与类型相关的(可能)挑战是什么?


在这种情况下,动态键入几乎不是最大的问题。对于python,这是动态范围。
SK-logic

值得注意的是,其他人认为在此平台上构建动态类型是正确的答案。正是由于这个原因,微软在DLR上投入了大量资金-数十年来,NeXT / Apple一直在推动这一潮流发展。这对CPython没有帮助,但是IronPython证明您可以有效地静态编译Python,而PyPy证明您不必这样做。
abarnert

2
@ SK-logic在Python中动态作用域?最后,我检查了该语言的所有构造均使用词法作用域。

1
@ SK-logic您可以动态创建代码并执行它,但是该代码还可以在词法范围内运行。对于Python程序中的每个单个变量,只需检查AST即可轻松确定该变量属于哪个范围。您可能正在考虑该exec声明,该声明自3.0版以来已过时,因此超出了我的考虑范围(可能是Guido的声明,因为此次演讲是2012年的)。你能举个例子吗?您对“动态作用域”的定义(如果它与我的定义不同)(en.wikipedia.org/wiki/Dynamic_scoping)。

1
@ SK-logic对我来说,唯一的实现细节是更改对的locals()持久化返回值locals。什么是记录,绝对不是一个实现细节是,甚至没有localsglobals可在范围内的每个变量在抬头改变。对于每一个使用一个变量,范围,这是指静态确定。这使得它在词法上确定了范围。(顺便说一句和,evalexec绝对不是实现细节或者- !看看我的回答)

Answers:


16

您在表述问题时过分简化了Guido的声明。问题不在于为动态类型的语言编写编译器。问题在于编写的代码(标准1)始终正确,(标准2)保持动态类型,(标准3)对于大量代码而言明显更快。

易于实现90%(失败标准1)的Python,并且始终保持快速。同样,使用静态类型(失败条件2)创建更快的Python变体很容易。100%的实现也很容易(在有限的情况下实现复杂的语言),但是到目前为止,每种简单的实现方式都相对较慢(失败标准3)。

实现正确的解释器和JIT,实现整个语言,并且对某些代码来说更快是可行的,尽管要困难得多(请参阅PyPy),而且只有在您自动创建JIT编译器时才能这样做(Psyco没有这样做) ,但是可以加快代码的速度非常有限)。但是请注意,这显然超出了范围,因为我们在谈论静态(又称提前)编译器。我仅提及这一点是为了解释为什么其方法不适用于静态编译器(或至少没有现有的反例):它首先必须解释和观察程序,然后为循环的特定迭代生成代码(或其他线性代码)路径),然后根据仅对特定迭代(或至少并非对所有可能的迭代)都成立的假设来优化地狱。期望该代码的许多后续执行也将与期望匹配,从而从优化中受益。添加了一些(相对便宜的)检查以确保正确性。为此,您需要了解专门针对什么的想法,并且需要缓慢但通用的实现方案。AOT编译器都没有。他们不能专注在所有根据他们看不到的代码(例如,动态加载的代码),不小心进行专业化意味着生成更多代码,这会带来很多问题(缓存利用率,二进制大小,编译时间,其他分支)。

实现正确实现整个语言的AOT编译器也相对容易:生成调用运行时的代码,以执行解释器在馈入此代码时将执行的操作。Nuitka(主要是)做到这一点。但是,这并不能带来太多的性能优势(失败标准3),因为您仍然需要做与解释程序一样多的不必要的工作,除了将字节码分派到C语言代码块中即可。这仅是相当小的成本-足以在现有的解释器中进行优化,但不足以证明有其自身问题的全新实施的合理性。

要满足所有三个条件,需要什么?我们不知道 有一些静态分析方案可以从Python程序中提取一些有关具体类型,控制流等的信息。那些产生超出单个基本块范围的准确数据的程序极其缓慢,需要查看整个程序,或者至少要查看整个程序。但是,除了优化内置类型的一些操作之外,您不能对这些信息做很多事情。

为什么?坦率地说,编译器要么没有能力执行在运行时加载的Python代码(失败标准1),要么根本没有做出任何可以被任何Python代码无效的假设。不幸的是,这几乎包含了所有对优化程序有用的东西:可以对函数进行全局修改,对类进行突变或完全替换,可以对模块进行任意修改,可以多种方式劫持导入等。将单个字符串传递给evalexec__import__或许多其他功能可以执行任何上述操作。实际上,这意味着几乎不能应用任何大的优化,而几乎没有性能收益(失败标准3)。回到上面的段落。


4

最困难的问题是弄清楚在任何给定时间所有事物的类型。

在像C或Java这样的静态语言中,一旦看到类型声明,就知道该对象是什么以及它可以做什么。如果声明了变量int,则为整数。例如,它不是可调用的函数引用。

在Python中可以。这是可怕的Python,但合法:

i = 2
x = 3 + i

def prn(s):
    print(s)

i = prn
i(x)

现在,这个例子很愚蠢,但是它说明了一般的想法。

更现实的是,您可以将内置函数替换为功能稍有不同的用户定义函数(例如,在调用该函数时会记录其参数的版本)。

PyPy在查看代码实际作用之后使用即时编译,这使PyPy大大加快了工作速度。PyPy可以监视一个循环,并验证每次循环运行时,变量foo始终是整数;然后PyPy可以优化foo在每次循环中查找类型的代码,甚至可以摆脱表示整数的Python对象,并且foo可以变成位于CPU寄存器中的数字。这就是PyPy可以比CPython更快的方式。CPython尽可能快地执行类型查找,但甚至不进行查找甚至更快。

我不知道细节,但是我记得有一个名为Unladen Swallow的项目正在尝试应用静态编译器技术来加速Python(使用LLVM)。您可能想在Google上搜索“空巢燕子”,看看是否可以找到关于为什么它未能如他们所愿进行讨论的讨论。


Unladen Swallow与静态编译或静态类型无关。最终的做法是将所有动态特性的CPython解释器有效地移植到LLVM,并使用了一个新的JIT(有点像Parrot或.NET的DLR或PyPy),尽管实际上它们最终在CPython中发现了很多本地优化(其中一些优化进入了主线3.x)。Shedskin可能是您正在考虑的项目,该项目使用静态类型推断来静态编译Python(尽管对于C ++,而不是直接针对本机代码)。
2013年

《空载燕子》的作者之一里德·克莱克纳(Reid Kleckner)发表了《空载燕子回顾》,在这种情况下可能值得一读,尽管实际上这更多是关于管理和赞助方面的挑战,而不是技术方面的挑战。
abarnert 2013年

0

正如另一个答案所说,关键问题是找出类型信息。如果您可以静态地执行此操作,则可以直接生成良好的代码。

但是,即使您不能静态地执行此操作,也仍然可以在运行时获得实际的类型信息时生成合理的代码。事实证明,此信息通常是稳定的,或者对于特定代码点处的任何特定实体,最多具有几个不同的值。在自编程语言开创了很多的积极的运行时类型的收集和运行时代码生成的想法。它的思想被广泛用于Java和C#等基于JIT的现代编译器中。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.