为什么numpy.array这么慢?
我对此感到困惑
def main():
for i in xrange(2560000):
a = [0.0, 0.0, 0.0]
main()
$ time python test.py
real 0m0.793s
现在我们来看看用numpy的情况:
import numpy
def main():
for i in xrange(2560000):
a = numpy.array([0.0, 0.0, 0.0])
main()
$ time python test.py
real 0m39.338s
天哪,CPU的运算真是太疯狂了!
使用 numpy.zeros(3)
确实有所改善,但在我看来还是不够
$ time python test.py
real 0m5.610s
user 0m5.449s
sys 0m0.070s
numpy.version.version = '1.5.1'
如果你在想第一个例子中是否为了优化而跳过了列表创建,那并没有:
5 19 LOAD_CONST 2 (0.0)
22 LOAD_CONST 2 (0.0)
25 LOAD_CONST 2 (0.0)
28 BUILD_LIST 3
31 STORE_FAST 1 (a)
4 个回答
虽然这个回答有点晚,但可能对其他观众很重要。
这个问题在kwant项目中也有考虑。实际上,numpy对小数组的优化并不好,而小数组恰好是你可能需要的。
为此,他们创建了一个小数组的替代品,这个替代品可以和numpy数组一起使用(在新数据类型中没有实现的操作会由numpy处理)。
你可以看看这个项目:
https://pypi.python.org/pypi/tinyarray/1.0.5
它的主要目的是让小数组的使用更加顺畅。当然,numpy的一些高级功能在这个替代品中是没有的。但看起来你更关注的是数值计算。
我做了一些小测试:
python
我添加了numpy的导入,以便正确获取加载时间
import numpy
def main():
for i in xrange(2560000):
a = [0.0, 0.0, 0.0]
main()
numpy
import numpy
def main():
for i in xrange(2560000):
a = numpy.array([0.0, 0.0, 0.0])
main()
numpy-zero
import numpy
def main():
for i in xrange(2560000):
a = numpy.zeros((3,1))
main()
tinyarray
import numpy,tinyarray
def main():
for i in xrange(2560000):
a = tinyarray.array([0.0, 0.0, 0.0])
main()
tinyarray-zero
import numpy,tinyarray
def main():
for i in xrange(2560000):
a = tinyarray.zeros((3,1))
main()
我运行了这个:
for f in python numpy numpy_zero tiny tiny_zero ; do
echo $f
for i in `seq 5` ; do
time python ${f}_test.py
done
done
得到了:
python
python ${f}_test.py 0.31s user 0.02s system 99% cpu 0.339 total
python ${f}_test.py 0.29s user 0.03s system 98% cpu 0.328 total
python ${f}_test.py 0.33s user 0.01s system 98% cpu 0.345 total
python ${f}_test.py 0.31s user 0.01s system 98% cpu 0.325 total
python ${f}_test.py 0.32s user 0.00s system 98% cpu 0.326 total
numpy
python ${f}_test.py 2.79s user 0.01s system 99% cpu 2.812 total
python ${f}_test.py 2.80s user 0.02s system 99% cpu 2.832 total
python ${f}_test.py 3.01s user 0.02s system 99% cpu 3.033 total
python ${f}_test.py 2.99s user 0.01s system 99% cpu 3.012 total
python ${f}_test.py 3.20s user 0.01s system 99% cpu 3.221 total
numpy_zero
python ${f}_test.py 1.04s user 0.02s system 99% cpu 1.075 total
python ${f}_test.py 1.08s user 0.02s system 99% cpu 1.106 total
python ${f}_test.py 1.04s user 0.02s system 99% cpu 1.065 total
python ${f}_test.py 1.03s user 0.02s system 99% cpu 1.059 total
python ${f}_test.py 1.05s user 0.01s system 99% cpu 1.064 total
tiny
python ${f}_test.py 0.93s user 0.02s system 99% cpu 0.955 total
python ${f}_test.py 0.98s user 0.01s system 99% cpu 0.993 total
python ${f}_test.py 0.93s user 0.02s system 99% cpu 0.953 total
python ${f}_test.py 0.92s user 0.02s system 99% cpu 0.944 total
python ${f}_test.py 0.96s user 0.01s system 99% cpu 0.978 total
tiny_zero
python ${f}_test.py 0.71s user 0.03s system 99% cpu 0.739 total
python ${f}_test.py 0.68s user 0.02s system 99% cpu 0.711 total
python ${f}_test.py 0.70s user 0.01s system 99% cpu 0.721 total
python ${f}_test.py 0.70s user 0.02s system 99% cpu 0.721 total
python ${f}_test.py 0.67s user 0.01s system 99% cpu 0.687 total
现在这些测试(如之前所提)并不是最好的测试。不过,它们仍然显示出tinyarray更适合小数组。
另一个事实是,最常见的操作在tinyarray中应该会更快。因此,它的使用可能比单纯的数据创建更有优势。
我从来没有在一个完整的项目中尝试过,但kwant项目正在使用它。
哇,CPU的运算真是惊人啊!
确实如此。
不过,请你考虑一下与numpy
相关的一些非常基础的东西;比如复杂的线性代数功能(像随机数
或者奇异值分解
)。现在,想想这些看似简单的计算:
In []: A= rand(2560000, 3)
In []: %timeit rand(2560000, 3)
1 loops, best of 3: 296 ms per loop
In []: %timeit u, s, v= svd(A, full_matrices= False)
1 loops, best of 3: 571 ms per loop
请相信我,这种性能是目前任何可用的包都无法轻易超越的。
所以,请描述一下你真正遇到的问题,我会尽量帮你找出一个不错的基于numpy
的解决方案。
更新:
这里有一些简单的代码,用于计算光线与球体的交点:
import numpy as np
def mag(X):
# magnitude
return (X** 2).sum(0)** .5
def closest(R, c):
# closest point on ray to center and its distance
P= np.dot(c.T, R)* R
return P, mag(P- c)
def intersect(R, P, h, r):
# intersection of rays and sphere
return P- (h* (2* r- h))** .5* R
# set up
c, r= np.array([10, 10, 10])[:, None], 2. # center, radius
n= 5e5
R= np.random.rand(3, n) # some random rays in first octant
R= R/ mag(R) # normalized to unit length
# find rays which will intersect sphere
P, b= closest(R, c)
wi= b<= r
# and for those which will, find the intersection
X= intersect(R[:, wi], P[:, wi], r- b[wi], r)
显然,我们的计算是正确的:
In []: allclose(mag(X- c), r)
Out[]: True
还有一些时间记录:
In []: % timeit P, b= closest(R, c)
10 loops, best of 3: 93.4 ms per loop
In []: n/ 0.0934
Out[]: 5353319 #=> more than 5 million detection's of possible intersections/ s
In []: %timeit X= intersect(R[:, wi], P[:, wi], r- b[wi])
10 loops, best of 3: 32.7 ms per loop
In []: X.shape[1]/ 0.0327
Out[]: 874037 #=> almost 1 million actual intersections/ s
这些时间是在一台非常普通的机器上测得的。用现代的机器,速度还会有显著提升。
总之,这只是一个简单的示范,展示了如何使用numpy
进行编程。
Numpy是为了处理大量数据而优化的。如果你给它一个只有3个元素的小数组,表现自然就不怎么样。
我们来看一个单独的测试:
import timeit
reps = 100
pythonTest = timeit.Timer('a = [0.] * 1000000')
numpyTest = timeit.Timer('a = numpy.zeros(1000000)', setup='import numpy')
uninitialised = timeit.Timer('a = numpy.empty(1000000)', setup='import numpy')
# empty simply allocates the memory. Thus the initial contents of the array
# is random noise
print 'python list:', pythonTest.timeit(reps), 'seconds'
print 'numpy array:', numpyTest.timeit(reps), 'seconds'
print 'uninitialised array:', uninitialised.timeit(reps), 'seconds'
输出结果是:
python list: 1.22042918205 seconds
numpy array: 1.05412316322 seconds
uninitialised array: 0.0016028881073 seconds
看起来是数组被清零的过程占用了Numpy大部分的时间。所以,如果你不需要数组初始化的话,可以试试用空数组。