为什么等效的Python代码要慢得多

时间:2010-11-29 16:01:13

标签: python ruby python-3.x

有人可以解释为什么以下琐碎的代码(Euclid算法的实现找到最大的共同点)比Ruby中的等效代码慢3倍?

iter_gcd.py的内容:

from sys import argv,stderr

def gcd(m, n):
    if n > m:
        m, n = n, m
    while n != 0:
        rem = m % n
        m = n
        n = rem
    return m

# in Python3 code there is xrange replaced with range function
def main(a1, a2):
    comp = 0
    for j in xrange(a1, 1, -1):
        for i in xrange(1, a2):
            comp += gcd(i,j)

    print(comp)

if __name__ == '__main__':
    if len(argv) != 3:
        stderr.write('usage: {0:s} num1 num2\n'.format(argv[0]))
        exit(1)
    else:
        main(int(argv[1]), int(argv[2]))

iter_gcd.rb的内容:

def gcd(m, n)
    while n != 0
        rem = m % n
        m = n
        n = rem
    end
    return m
end

def main(a1, a2)
    comp = 0
    a1.downto 2 do
        |j|
        1.upto (a2 - 1) do
            |i|
            comp += gcd(i,j)
        end
    end
    puts comp
end

 if __FILE__ == $0
    if ARGV.length != 2
        $stderr.puts('usage: %s num1 num2' % $0)
        exit(1)
    else
        main(ARGV[0].to_i, ARGV[1].to_i)
    end
end

执行时间测量:

$ time python iter_gcd.py 4000 3000
61356305

real    0m22.890s
user    0m22.867s
sys     0m0.006s

$ python -V
Python 2.6.4


$ time python3 iter_gcd.py 4000 3000
61356305

real    0m18.634s
user    0m18.615s
sys     0m0.009s

$ python3 -V
Python 3.1.2


$ time ruby iter_gcd.rb 4000 3000
61356305

real    0m7.619s
user    0m7.616s
sys     0m0.003s

$ ruby -v
ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-linux]

好奇为什么我得到了这样的结果。我认为CPython在大多数情况下比MRI更快,甚至是YARV上的新Ruby 1.9,但这个“微基准”确实让我感到惊讶。

顺便说一句,我知道我可以使用像fractions.gcd这样的专业库函数,但我想比较一下这些基本和普通语言结构的实现。

我是否遗漏了一些东西,或者下一代Ruby的实现在绝对速度方面有了如此大的改进?

4 个答案:

答案 0 :(得分:36)

摘要

“因为Python中的函数调用开销远大于Ruby。”

详细

作为一个微基准测试,这对于正确使用这两种语言的性能并没有多少说明。可能你会想要重写程序以利用Python和Ruby的优势,但这确实说明了Python目前的一个弱点。速度差异的根本原因来自函数调用开销。我做了一些测试来说明。请参阅下面的代码和更多详细信息。对于Python测试,我使用2000作为两个gcd参数。

Interpreter: Python 2.6.6
Program type: gcd using function call
Total CPU time: 29.336 seconds

Interpreter: Python 2.6.6
Program type: gcd using inline code
Total CPU time: 13.194 seconds

Interpreter: Python 2.6.6
Program type: gcd using inline code, with dummy function call
Total CPU  time: 30.672 seconds

这告诉我们,gcd函数所做的计算不是时间差的最大因素,而是函数调用本身。使用Python 3.1,差异是相似的:

Interpreter: Python 3.1.3rc1
Program type: gcd using function call
Total CPU time: 30.920 seconds

Interpreter: Python 3.1.3rc1
Program type: gcd using inline code
Total CPU time: 15.185 seconds

Interpreter: Python 3.1.3rc1
Program type: gcd using inline code, with dummy function call
Total CPU time: 33.739 seconds

同样,实际计算不是最大的贡献者,它是函数调用本身。在Ruby中,函数调用开销要小得多。 (注意:我必须对程序的Ruby版本使用较小的参数(200),因为Ruby分析器确实会降低实时性能。但这并不会影响CPU时间性能。)

Interpreter: ruby 1.9.2p0 (2010-08-18 revision 29036) [i486-linux]
Program type: gcd using function call
Total CPU time: 21.66 seconds

Interpreter: ruby 1.9.2p0 (2010-08-18 revision 29036) [i486-linux]
Program type: gcd using inline code
Total CPU time: 21.31 seconds

Interpreter: ruby 1.8.7 (2010-08-16 patchlevel 302) [i486-linux]
Program type: gcd using function call
Total CPU time: 27.00 seconds

Interpreter: ruby 1.8.7 (2010-08-16 patchlevel 302) [i486-linux]
Program type: gcd using inline code
Total CPU time: 24.83 seconds

注意Ruby 1.8和1.9都不会受到gcd函数调用的影响 - 函数调用与内联版本或多或少相等。 Ruby 1.9似乎更好一点,函数调用和内联版本之间的差异较小。

所以这个问题的答案是:“因为Python中的函数调用开销远大于Ruby中的函数。”

代码

# iter_gcd -- Python 2.x version, with gcd function call
#             Python 3.x version uses range instead of xrange
from sys import argv,stderr

def gcd(m, n):
    if n > m:
        m, n = n, m
    while n != 0:
        rem = m % n
        m = n
        n = rem
    return m

def main(a1, a2):
    comp = 0
    for j in xrange(a1, 1, -1):
        for i in xrange(1, a2):
            comp += gcd(i,j)
    print(comp)

if __name__ == '__main__':
    if len(argv) != 3:
        stderr.write('usage: {0:s} num1 num2\n'.format(argv[0]))
        exit(1)
    else:
        main(int(argv[1]), int(argv[2]))

# iter_gcd -- Python 2.x version, inline calculation
#             Python 3.x version uses range instead of xrange
from sys import argv,stderr

def main(a1, a2):
    comp = 0
    for j in xrange(a1, 1, -1):
        for i in xrange(1, a2):
            if i < j:
                m, n = j, i
            else:
                m, n = i, j
            while n != 0:
                rem = m % n
                m = n
                n = rem
            comp += m
    print(comp)

if __name__ == '__main__':
    if len(argv) != 3:
        stderr.write('usage: {0:s} num1 num2\n'.format(argv[0]))
        exit(1)
    else:
        main(int(argv[1]), int(argv[2]))

# iter_gcd -- Python 2.x version, inline calculation, dummy function call
#             Python 3.x version uses range instead of xrange
from sys import argv,stderr

def dummyfunc(n, m):
    a = n + m

def main(a1, a2):
    comp = 0
    for j in xrange(a1, 1, -1):
        for i in xrange(1, a2):
            if i < j:
                m, n = j, i
            else:
                m, n = i, j
            while n != 0:
                rem = m % n
                m = n
                n = rem
            comp += m
            dummyfunc(i, j)
    print(comp)

if __name__ == '__main__':
    if len(argv) != 3:
        stderr.write('usage: {0:s} num1 num2\n'.format(argv[0]))
        exit(1)
    else:
        main(int(argv[1]), int(argv[2]))

# iter_gcd -- Ruby version, with gcd function call

def gcd(m, n)
    if n > m
        m, n = n, m
    end
    while n != 0
        rem = m % n
        m = n
        n = rem
    end
    return m
end

def main(a1, a2)
    comp = 0
    a1.downto 2 do
        |j|
        1.upto a2-1 do
            |i|
            comp += gcd(i,j)
        end
    end
    puts comp
end

 if __FILE__ == $0
    if ARGV.length != 2
        $stderr.puts('usage: %s num1 num2' % $0)
        exit(1)
    else
        main(ARGV[0].to_i, ARGV[1].to_i)
    end
end

# iter_gcd -- Ruby version, with inline gcd

def main(a1, a2)
    comp = 0
    a1.downto 2 do |j|
        1.upto a2-1 do |i|
            m, n = i, j
            if n > m
                m, n = n, m
            end
            while n != 0
                rem = m % n
                m = n
                n = rem
            end
            comp += m
        end
    end
    puts comp
end

 if __FILE__ == $0
    if ARGV.length != 2
        $stderr.puts('usage: %s num1 num2' % $0)
        exit(1)
    else
        main(ARGV[0].to_i, ARGV[1].to_i)
    end
end

测试运行

最后,用于运行Python和Ruby并使用性能分析来获取比较数的命令是Python的pythonX.X -m cProfile iter_gcdX.py 2000 2000和Ruby的rubyX.X -rprofile iter_gcdX.rb 200 200。造成这种差异的原因是Ruby分析器增加了很多开销。结果仍然有效,因为我正在比较函数调用和内联代码之间的区别,而不是Python和Ruby之间的区别。

另见

Why is python slower compared to Ruby even with this very simple “test”?

Is there something wrong with this python code, why does it run so slow compared to ruby?

The Computer Language Benchmarks Game

Google Search: ruby python function call faster

答案 1 :(得分:22)

对于my machine上的“微基准”,我可以确认ruby1.9比CPython更快:

| Interpreter                     | Time, s | Ratio |
|---------------------------------+---------+-------|
| python-2.6 (cython_gcd.gcd_int) |     2.8 |  0.33 |
| pypy-1.4                        |     3.5 |  0.41 |
| jython-2.5 (java "1.6.0_20")    |     4.7 |  0.55 |
| python-2.6 (cython_gcd.gcd)     |     5.6 |  0.65 |
| ruby-1.9                        |     8.6 |  1.00 |
| jython-2.5                      |     8.9 |  1.03 |
| python-3.2                      |    11.0 |  1.28 |
| python-2.6                      |    15.9 |  1.85 |
| ruby-1.8                        |    42.6 |  4.95 |
#+TBLFM: $3=$2/@6$2;%.2f

Profiler(python -mcProfile iter_gcd.py 4000 3000)显示80%的时间用于调用gcd()函数,因此确实差异在于gcd()函数。

我使用Cython为cython_gcd编写了cython_gcd.pyx扩展名def gcd(m, n): while n: n, m = m % n, n return m def gcd_int(int m, int n): while n: n, m = m % n, n return m

iter_gcd.py

它在from cython_gcd import gcd, gcd_int中使用如下python setup.py build_ext --inplace

要尝试扩展,请运行:from distutils.core import setup from distutils.extension import Extension from Cython.Distutils import build_ext ext_modules = [Extension("cython_gcd", ["cython_gcd.pyx"])] setup( name = 'Hello world app', cmdclass = {'build_ext': build_ext}, ext_modules = ext_modules ) ,其中setup.py

python setup.py install

要全局安装扩展程序,请运行{{1}}。

答案 2 :(得分:11)

我似乎记得ruby处理整数的方式与Python不同,所以我的猜测就是Python只花费大量时间分配内存,而Ruby只是改变了整数。

对于它的价值,使用Pypy 1.4可以将我系统上Python版本的运行时间从大约15秒减少到3秒以内。

答案 3 :(得分:0)

我无法复制你的结果。 python代码似乎比ruby代码快4倍:

2010-12-07 13:49:55:~/tmp$ time python  iter_gcd.py 4000 3000
61356305

real    0m14.655s
user    0m14.633s
sys 0m0.012s

2010-12-07 13:43:26:~/tmp$ time ruby iter_gcd.rb 4000 3000
iter_gcd.rb:14: warning: don't put space before argument parentheses
61356305

real    0m54.298s
user    0m53.955s
sys 0m0.028s

版本:

2010-12-07 13:50:12:~/tmp$ ruby --version
ruby 1.8.7 (2010-06-23 patchlevel 299) [i686-linux]
2010-12-07 13:51:52:~/tmp$ python --version
Python 2.6.6

此外,python代码可以快8%:

def gcd(m, n):
    if n > m:
        m, n = n, m
    while n:
        n, m = m % n, n
    return m

def main(a1, a2):
    print sum(
        gcd(i,j)
        for j in xrange(a1, 1, -1)
        for i in xrange(1, a2)
    )

if __name__ == '__main__':
    from sys import argv
    main(int(argv[1]), int(argv[2]))

后来:当我安装并使用ruby 1.9.1时,ruby代码更快:

2010-12-07 14:01:08:~/tmp$ ruby1.9.1 --version
ruby 1.9.2p0 (2010-08-18 revision 29036) [i686-linux]
2010-12-07 14:01:30:~/tmp$ time ruby1.9.1 iter_gcd.rb 4000 3000
61356305

real    0m12.137s
user    0m12.037s
sys 0m0.020s

我认为你的问题确实是,“为什么ruby 1.9.x比ruby 1.8.x快得多?”