我们正在使用Python 制作一些kNN
和SVD
实现.其他人选择了Java.我们的执行时间非常不同.我使用cProfile来查看我在哪里犯错但实际上一切都很好.是的,我numpy
也用.但我想问一个简单的问题.
total = 0.0 for i in range(9999): # xrange is slower according for j in range(1, 9999): #to my test but more memory-friendly. total += (i / j) print total
这段代码在我的电脑上占用了31.40秒.
此代码的Java版本在同一台计算机上占用1秒或更短时间.我想,类型检查是这段代码的主要问题.但我应该为我的项目做很多这样的操作,我认为9999*9999不是那么大的数字.
我想我犯了错误,因为我知道Python被很多科学项目所使用.但是为什么这段代码这么慢,我怎么能处理比这更大的问题呢?
我应该使用JIT编译器Psyco
吗?
我还说这个循环问题只是一个例子.代码并不像这样简单,可能很难将改进/代码示例付诸实践.
另一个问题是,我可以实现大量的数据挖掘和机器学习算法与numpy
和scipy
,如果我正确地使用它?
我想我犯了错误,因为我知道Python被很多科学项目所使用.
他们大量使用SciPy(NumPy是最突出的组件,但我听说围绕NumPy的API开发的生态系统更为重要),这大大加快了这些项目所需的各种操作.你做错了什么:你不是用C 编写你的关键代码.一般来说,Python非常适合开发,但是扩展良好的扩展模块本身就是一个至关重要的优化(至少当你处理数字时) .Python是一种非常糟糕的语言,用于实现紧密的内部循环.
默认(当时最受欢迎和广泛支持的)实现是一个简单的字节码解释器.即使是最简单的操作,如整数除法,也可能需要数百个CPU周期,多个内存访问(类型检查是一个流行的例子),几个C函数调用等,而不是几个(甚至单个,在整数的情况下)分裂)指令.此外,该语言设计有许多抽象,增加了开销.如果你使用xrange,你的循环会在堆上分配9999个对象 - 如果你使用range
(9999*9999整数减去256*256左右,对于缓存的小整数)则更多.此外,该xrange
版本在每次迭代时调用一个方法来推进 - range
如果序列上的迭代没有被特别优化,那么版本也是如此.它仍然需要一个完整的字节码调度,这本身就非常复杂(当然,与整数除法相比).
看看什么是JIT会很有趣(我推荐PyPy而不是Psyco,后者不再是主动开发的,而且范围非常有限 - 尽管如此,它可能适用于这个简单的例子).经过一小部分迭代之后,它应该产生一个最优的机器代码循环,增加一些防护 - 简单的整数比较,如果它们失败则跳跃 - 以保持正确性,以防你在该列表中得到一个字符串.Java可以做到同样的事情,只是更早(它不必先跟踪)和更少的防护(至少如果你使用int
s).这就是它快得多的原因.
因为你提到科学代码,看看numpy
.您正在做的事情可能已经完成(或者更确切地说,它使用LAPACK来处理像SVD这样的事情).当你听说python被用于科学代码时,人们可能并不是指你在你的例子中使用它.
作为一个简单的例子:
(如果你使用python3,你的例子会使用浮点除法.我的例子假设你使用python2.x,因此整数除法.如果不是,请指定i = np.arange(9999, dtype=np.float)
等)
import numpy as np i = np.arange(9999) j = np.arange(1, 9999) print np.divide.outer(i,j).sum()
给出时间的一些想法...(我将在这里使用浮点除法,而不是像你的例子中的整数除法):
import numpy as np def f1(num): total = 0.0 for i in range(num): for j in range(1, num): total += (float(i) / j) return total def f2(num): i = np.arange(num, dtype=np.float) j = np.arange(1, num, dtype=np.float) return np.divide.outer(i, j).sum() def f3(num): """Less memory-hungry (and faster) version of f2.""" total = 0.0 j = np.arange(1, num, dtype=np.float) for i in xrange(num): total += (i / j).sum() return total
如果我们比较时间:
In [30]: %timeit f1(9999) 1 loops, best of 3: 27.2 s per loop In [31]: %timeit f2(9999) 1 loops, best of 3: 1.46 s per loop In [32]: %timeit f3(9999) 1 loops, best of 3: 915 ms per loop
Python的好处是,与Java(你只有这种反射机制)相比,它有更多的灵活性(例如,类是对象)
这里没有提到的是Cython.它允许引入类型变量并将您的示例转换为C/C++.然后它快得多.我也改变了循环中的界限......
from __future__ import division cdef double total = 0.00 cdef int i, j for i in range(9999): for j in range(1, 10000+i): total += (i / j) from time import time t = time() print("total = %d" % total) print("time = %f[s]" % (time() - t))
其次是
$ cython loops.pyx $ gcc -I/usr/include/python2.7 -shared -pthread -fPIC -fwrapv -Wall -fno-strict-aliasing -O3 -o loops.so loops.c $ python -c "import loops"
给
total = 514219068 time = 0.000047[s]