为什么Python和Ruby很慢,而Lisp的实现很快?

2024-05-13 16:33:18 发布

您现在位置:Python中文网/ 问答频道 /正文

我发现在Python和Ruby中,函数调用和循环之类的简单事情,甚至只是循环递增一个计数器所需的时间都比Chicken Scheme、Racket或SBCL中的要长。

为什么会这样?我经常听到人们说,慢是动态语言的代价,但是lisp是非常动态的,并不是非常慢(它们通常比C慢不到5倍;Ruby和Python可以达到两位数)。此外,Lisp风格使用递归,并不总是尾部递归,很多情况下,堆栈是堆中连续性的链表,等等,这似乎会使Lisp比命令式Python和Ruby慢。

Racket和SBCL是JITted的,但是Chicken Scheme要么是静态编译的,要么是使用非优化的解释器,这两种解释器都不适合动态语言,而且速度慢。然而,即使使用Chicken Scheme的天真的csi解释器(它甚至不进行字节码编译!),我的速度远远超过Python和Ruby。

与类似的动态lisp相比,Python和Ruby到底为什么慢得可笑?是不是因为它们是面向对象的,需要巨大的vtable和类型继承器?

例如:阶乘函数。Python:

def factorial(n):
    if n == 0:
        return 1
    else:
    return n*factorial(n-1)

for x in xrange(10000000):
    i = factorial(10)

球拍:

#lang racket

(define (factorial n)
  (cond
   [(zero? n) 1]
   [else (* n (factorial (sub1 n)))]))

(define q 0)

(for ([i 10000000])
  (set! q (factorial 10)))

计时结果:

ithisa@miyasa /scratch> time racket factorial.rkt
racket factorial.rkt  1.00s user 0.03s system 99% cpu 1.032 total
ithisa@miyasa /scratch> time python factorial.py
python factorial.py  13.66s user 0.01s system 100% cpu 13.653 total

Tags: 语言forreturn动态解释器elseschemeruby
3条回答

我不知道您的racket安装,但如果运行时没有标志,我只是apt-get install使用JIT编译的racket。使用--no-jit运行会使时间更接近Python时间(racket:3s,racket --no-jit:37s,python:74s)。此外,由于语言设计的原因,模块范围内的赋值比Python中的本地赋值慢(非常自由的模块系统),将代码移到一个函数中会使Python处于60秒。剩下的差距可能可以解释为一些巧合、不同优化焦点的组合(函数调用在Lisp中必须非常快,Python人不太在意)、实现质量(ref计数与正确的GC、stack VM与register VM)等等,而不是各自语言设计的基本结果。

编译后的Lisp系统通常比Ruby或Python快得多。

例如,请参见Ruby和SBCL的比较:

http://benchmarksgame.alioth.debian.org/u32/benchmark.php?test=all&lang=yarv&lang2=sbcl&data=u32

或者Python和SBCL:

http://benchmarksgame.alioth.debian.org/u32/benchmark.php?test=all&lang=python3&lang2=sbcl&data=u32

但请记住以下几点:

  • SBCL使用本机代码编译器。它不使用字节码机器或类似JIT编译器的东西,从字节码到本机代码。SBCL在运行前将所有代码从源代码编译为本机代码。编译器是增量的,可以编译单个表达式。因此,EVAL函数和Read EVAL Print循环也使用它。
  • SBCL使用一个优化编译器,它使用类型声明和类型推断。编译器生成本机代码。
  • Common Lisp允许各种优化,这些优化使代码不太动态或不动态(内联、早期绑定、不进行类型检查、为声明类型专用的代码、尾部调用优化,…)。使用这些高级功能的代码可能看起来很复杂,特别是当需要告诉编译器这些事情的时候。
  • 如果没有这些优化,编译的Lisp代码仍然比解释的代码快,但比优化的编译代码慢。
  • Common Lisp提供了CLOS,即commonlisp对象系统。CLOS代码通常比非CLOS慢—在这种情况下,比较是有意义的。动态函数语言往往比动态面向对象语言快。
  • 如果语言实现使用高度优化的运行时,例如bignum算术操作,那么慢的语言实现可能比优化编译器快。有些语言有许多复杂的原语是用C语言实现的,这些原语往往很快,而其他的语言则很慢。

另外,一些操作可能看起来相似,但可能不同。迭代整数变量的for循环真的和迭代范围的for循环一样吗?

Ruby/Python/etc中的方法调度非常昂贵,Ruby/Python/etc程序主要通过调用方法进行计算。甚至Ruby中的for循环对于对each的方法调用来说也是语法糖。

相关问题 更多 >