有人可以解释为什么以下琐碎的代码(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的实现在绝对速度方面有了如此大的改进?
答案 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?
答案 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快得多?”