为什么numpy.array这么慢?

31 投票
4 回答
27285 浏览
提问于 2025-04-16 20:43

我对此感到困惑

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 个回答

2

虽然这个回答有点晚,但可能对其他观众很重要。

这个问题在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项目正在使用它。

4

哇,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进行编程。

46

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大部分的时间。所以,如果你不需要数组初始化的话,可以试试用空数组。

撰写回答