为什么像Python和Ruby这样的语言被解释(开箱即用)而不是编译的技术原因是什么?在我看来,对于熟悉这一领域的人来说,不应该像现在这样解释这些语言,我们会看到显着的性能提升。所以我肯定错过了一些东西。
答案 0 :(得分:32)
有几个原因:
想想如果系统不解释会发生什么。假设您使用translation-to-C作为机制。编译后的代码会定期检查它是否已被元编程取代。类似的情况出现在eval()
- 类型的函数中。在这些情况下,它必须再次运行编译器,一个极其缓慢的过程,或者它必须也在运行时具有解释器。
这里唯一的选择是JIT编译器。这些系统非常复杂和复杂,并且具有比所有其他替代方案更大的运行时间。它们启动速度非常慢,使得它们对于脚本编写起来不切实际。曾经见过Java脚本吗?我没有。
所以,你有两个选择:
通常情况下,主要实施方式与第二种选择一致,这并不奇怪。有一天我们可能会看到像编译器这样的二次实现出现。 Ruby 1.9和Python有字节码VM;那些是½方式。编译器可能只针对非动态代码,或者它可能具有各种级别的语言支持,可以作为选项声明。但是,由于这样的事情不能成为主要的实施方式,因此它代表了许多工作,以获得非常小的利益。 Ruby已经拥有200,000行C ......
我想我应该补充一点,总是可以添加一个已编译的C(或者,通过一些努力,任何其他语言)扩展。所以,假设你有一个缓慢的数值运算。如果你添加,用{C}实现说Array#newOp
,那么你得到加速,程序保留在Ruby(或其他),你的环境获得一个新的实例方法。大家都赢了!因此,这减少了对有问题的二次实施的需求。
答案 1 :(得分:16)
完全像(在典型的实现中)Java或C#,Python首先被编译成某种形式的字节码,具体取决于实现(CPython使用自己的专用形式,Jython使用JVM就像典型的Java,IronPython像典型的C#一样使用CLR,等等 - 然后进一步处理该字节码以供虚拟机(AKA解释器)执行,该虚拟机也可以“及时”生成机器代码 - 称为JIT - 如果在有保证的情况下(CLR和JVM实现经常这样做,CPython自己的虚拟机通常不能,但可以使用psyco或Unladen Swallow来实现)。
JIT可以为足够长时间运行的程序(如果内存比CPU周期更便宜)付出代价,但它可能不会(由于较慢的启动时间和较大的内存占用),尤其是当必须推断类型或专门作为代码生成的一部分。如果这是您想要的,例如,生成没有类型推断或专业化的机器代码是很容易的。 freeze为你做了这件事,但它确实没有提出“机器代码欺骗”属性的优点。例如,你得到一个1.5到2 MB的可执行二进制代替一个微小的“hello world”.pyc
- 没什么意义! - )。该可执行文件是独立的,可以自行分发,但它只适用于非常特定的操作系统和CPU体系结构,因此在大多数情况下,这种权衡是非常不确定的。并且,准备可执行文件所花费的时间确实很长,因此将该操作模式设置为默认操作模式将是一个疯狂的选择。
答案 2 :(得分:8)
仅仅使用编译器替换解释器不会像Python那样的语言那样给你带来如此大的性能提升。当大部分时间实际花费在字典中对对象成员进行符号查找时,如果对执行此类查找的函数的调用被解释,或者是本机机器代码并不重要 - 差异虽然不是可以忽略不计,但却相形见绌。通过查询开销。
要真正提高性能,您需要优化编译器。这里的优化技术与你使用C ++,甚至Java JIT都有很大的不同 - 对于动态类型/鸭类型语言(如Python)的优化编译器需要做一些非常有创意的类型推断(包括概率 - 即“90%的几率”它是T“然后为那个案例生成有效的机器代码,前面有一个检查/分支)和逃逸分析。这很难。
答案 3 :(得分:6)
我认为解释语言的最大原因是可移植性。作为程序员,您可以编写将在解释器而非特定操作系统中运行的代码。因此,您的程序在不同平台上的表现更加统一(比编译语言更多)。我能想到的另一个优点是在解释语言中使用动态类型系统更容易。我认为语言的创造者正在考虑使用一种语言,程序员可以通过自动内存管理,动态类型系统和元编程获得更高效率,因为语言被解释会导致任何性能损失。如果您担心性能,可以使用JIT编译等技术将语言编译为本机机器代码。
答案 4 :(得分:5)
今天,“编译”和“解释”语言之间不再存在强烈的区别。事实上Python的编译与Java一样多,唯一的区别是:
Python甚至还有一个名为compile()
的函数,它是编译器的接口。
听起来,您所做的区别在于“动态类型”和“静态类型”语言之间。在Python等动态语言中,您可以编写如下代码:
def fn(x, y):
return x.foo(y)
请注意,未指定x
和y
的类型。在运行时,此函数将查看x
以查看它是否具有名为foo
的成员函数,如果是,则将其与y
一起调用。如果没有,它将抛出一个运行时错误,表明没有找到这样的函数。这种运行时查找使用像字节码这样的中间表示更容易表示,其中运行时VM执行查找而不必生成机器代码来执行查找本身(或者,调用函数来执行查找,这就是字节码无论如何都会这样做。)
Python有Psyco,PyPy和Unladen Swallow等项目,它们采用各种方法将Python对象代码编译成更接近本机代码的东西。在这个领域有积极的研究,但还没有(现在)一个简单的答案。
答案 5 :(得分:5)
创建一个好的编译器来为新语言生成本机代码所需的工作是错开。小型研究小组通常需要5到10年(例如:SML/NJ,Haskell,Clean,Cecil,lcc,Objective Caml,MLton以及许多其他小组。当有问题的语言需要在运行时进行类型检查和其他决策时,编译器编写者必须更加努力才能获得良好的本机代码性能(例如,请参阅Craig Chambers和后来的Urs Hoelzle的工作。自)。 您可能希望的性能提升比您想象的更难实现。这种现象部分解释了why so many dynamically typed languages are interpreted。
如上所述,一个体面的解释器也可以立即移植,而将编译器移植到新的机器架构需要花费很多精力(这是我个人已经工作了20多年的问题,并且有一段时间可以保持良好的行为)。因此,口译员可以快速覆盖广大受众。
最后,尽管存在快速编译器和慢速解释器,但通过使用解释器使编辑 - 转换 - 循环更快,通常更容易。 (有关快速编译器的一些很好的示例,请参阅前面提到的lcc以及Ken Thompson的go编译器。有关解释速度相对较慢的示例,请参阅GHCi。
答案 6 :(得分:2)
那么,这些语言的优势之一是它们如此容易编写脚本吗?如果他们被编译,他们就不会。而另一方面,动态语言比编译更易于互相干预。
答案 7 :(得分:2)
在编译语言中,制作软件时遇到的循环是
这不一定是python | ruby设计师想到的原因,但要记住“机器运行效率有多高?”只是软件开发问题的一半。
使用自然解释的语言编译代码似乎更容易,而不是将解释器添加到默认编译的语言中。
答案 8 :(得分:2)
REPL。在你尝试之前不要敲它。 :)
答案 9 :(得分:1)
按设计。
作者想要一些可以编写脚本的东西。
Python第一次执行时会编译
答案 10 :(得分:1)
至少编译Ruby是非常困难的。我正在研究一个,作为其中的一部分,我写了一篇博文enumerating some of the issues here。
具体来说,Ruby在程序的“读取”和“执行”阶段之间存在非常不清楚(即不存在)的边界,这使得难以有效地编译。你可以模仿翻译的功能,但是你不会看到很多加速,所以不值得努力。如果你想有效地编译它,那么你将面临许多额外的复杂性来处理Ruby中的极端动态。
好消息是 技术可以克服这个问题。 Self,Smalltalk和Lisp / Scheme已经成功解决了大多数相同的问题。但是需要时间来筛选它并弄清楚如何使它与Ruby一起工作。 Ruby也有一个非常复杂的语法也无济于事。
答案 11 :(得分:1)
原始计算性能可能不是大多数解释语言的目标。解释语言通常更关心程序员的生产力而不是原始速度。在大多数情况下,这些语言足够快,足以完成语言设计要解决的任务。
考虑到这一点,并且编译器的唯一优点是类型检查(在动态语言中很难做到)和速度,为大多数解释语言编写编译器的动机并不大。