为什么(python|ruby)是解释型语言?
为什么像Python和Ruby这样的编程语言是直接解释执行的,而不是先编译再运行呢?我觉得对于懂行的人来说,把这些语言改成编译型的应该不难,这样我们就能看到明显的性能提升。所以我一定是漏掉了什么重要的原因。
12 个回答
单单把解释器换成编译器,并不会像你想的那样大幅提升像Python这样的语言的性能。因为大部分时间其实是用来在字典中查找对象成员的,这时候调用执行查找的函数是用解释方式还是用机器码执行,其实差别不大。虽然有点区别,但相比查找的开销,这点差别就显得微不足道了。
要真正提升性能,你需要优化过的编译器。而这里的优化技术和C++或者Java的即时编译(JIT)是非常不同的。对于像Python这样的动态类型或鸭子类型语言,优化编译器需要进行一些非常有创意的类型推断(包括概率性推断,比如“90%可能是T类型”),然后在这种情况下生成高效的机器码,并在执行前进行检查和分支。这是非常困难的。
就像Java或C#的常见实现一样,Python首先会被编译成一种字节码,这种字节码的形式取决于具体的实现方式(比如,CPython使用自己特定的字节码,Jython则像普通的Java一样使用JVM,IronPython则像普通的C#一样使用CLR,等等)。然后,这些字节码会被一个虚拟机(也叫解释器)进一步处理以便执行,这个虚拟机在需要的时候可能还会生成机器码,这种方式叫做“即时编译”(JIT)。通常,CLR和JVM的实现会使用这种方式,而CPython的虚拟机通常不会,但可以通过一些工具(比如psyco或Unladen Swallow)来实现。
对于运行时间较长的程序来说,JIT可能会带来好处(因为内存比CPU周期便宜),但也可能没有好处(因为启动时间较慢和占用更多内存),尤其是在代码生成时需要推断或特化类型的情况下。如果你只想生成机器码而不进行类型推断或特化,那其实很简单,比如freeze就可以帮你做到,但这样做并不会带来“机器码爱好者”所说的那些优势。例如,你可能会得到一个1.5到2MB的可执行文件,而不是一个小小的“hello world” .pyc
文件,这样做其实没什么意义!这个可执行文件是独立的,可以分发,但它只能在非常特定的操作系统和CPU架构上运行,所以大多数情况下,这样的权衡并不划算。而且,准备这个可执行文件的时间也相当长,所以把这种操作模式设为默认选择真的是个疯狂的决定。
有几个原因:
- 开发速度更快,写-测 和 写-编译-链接-测 相比,前者更简单。
- 更容易实现动态行为(比如反射和元编程)。
- 整个系统更便于移植(只需重新编译底层的C代码,就能在新平台上运行)。
想象一下如果系统不是解释型的会发生什么。假设你用把代码翻译成C的方式来运行。编译后的代码需要定期检查自己是否被元编程的内容取代。这种情况在使用 eval()
这类函数时也会出现。在这些情况下,它要么得重新运行编译器,这个过程非常慢,要么就必须在运行时也保留解释器。
这里唯一的替代方案是即时编译器(JIT)。这些系统非常复杂,运行时占用的资源比其他方案还要多。它们启动得很慢,因此不适合用在脚本中。你见过Java脚本吗?我没见过。
所以,你有两个选择:
- 同时承受编译器和解释器的所有缺点。
- 只承受解释器的缺点。
所以一般来说,主要的实现方式选择了第二种。这并不奇怪。未来可能会出现像编译器这样的次要实现。Ruby 1.9和Python有字节码虚拟机;这算是走了一半的路。编译器可能只针对非动态代码,或者可以根据需要选择不同的语言支持。但因为这种方式不能作为主要实现,所以它的工作量大而收益小。Ruby里面已经有20万行C代码了……
我想补充一下,你总是可以添加一个编译的C扩展(或者经过一些努力,其他语言的扩展)。比如说你有一个慢的数值运算。如果你添加一个用C实现的 Array#newOp
,那么你就能提高速度,程序依然在Ruby(或其他语言)中运行,你的环境也会得到一个新的实例方法。大家都能受益!这样就减少了对麻烦的次要实现的需求。