Numpy数组索引和/或加法似乎很慢
我在玩弄numpy数组的性能测试,因为我发现当我试图用numpy数组替换python数组时,结果比预期的要慢。
我知道我可能漏掉了什么,希望有人能帮我解答一下。
我创建了两个函数并对它们进行了计时。
NUM_ITERATIONS = 1000
def np_array_addition():
np_array = np.array([1, 2])
for x in xrange(NUM_ITERATIONS):
np_array[0] += x
np_array[1] += x
def py_array_addition():
py_array = [1, 2]
for x in xrange(NUM_ITERATIONS):
py_array[0] += x
py_array[1] += x
结果:
np_array_addition: 2.556 seconds
py_array_addition: 0.204 seconds
这是怎么回事?是什么导致了这么大的速度下降?我以为如果我使用的是固定大小的数组,numpy的速度至少应该是一样的。
谢谢!
更新:
我一直觉得numpy数组的访问速度慢,我想:“嘿,它们不就是内存中的数组吗?Cython应该能解决这个问题!”
结果确实解决了。以下是我修订后的性能测试:
import numpy as np
cimport numpy as np
ctypedef np.int_t DTYPE_t
NUM_ITERATIONS = 200000
def np_array_assignment():
cdef np.ndarray[DTYPE_t, ndim=1] np_array = np.array([1, 2])
for x in xrange(NUM_ITERATIONS):
np_array[0] += 1
np_array[1] += 1
def py_array_assignment():
py_array = [1, 2]
for x in xrange(NUM_ITERATIONS):
py_array[0] += 1
py_array[1] += 1
我把np_array
重新定义为cdef np.ndarray[DTYPE_t, ndim=1]
print(timeit(py_array_assignment, number=3))
# 0.03459
print(timeit(np_array_assignment, number=3))
# 0.00755
这时,python函数也通过cython进行了优化。纯python的函数计时是:
print(timeit(py_array_assignment, number=3))
# 0.12510
速度提升了17倍。虽然这是个简单的例子,但我觉得很有教育意义。
2 个回答
如果你在Python中使用循环,那其实并没有真正利用到numpy的数组加法功能;还有@shashkello提到的访问开销问题。
我稍微增加了一下数组的大小,并且添加了一个使用向量化的加法版本:
import numpy as np
from timeit import timeit
NUM_ITERATIONS = 1000
def np_array_addition():
np_array = np.array(xrange(1000))
for x in xrange(NUM_ITERATIONS):
for i in xrange(len(np_array)):
np_array[i] += x
def np_array_addition2():
np_array = np.array(xrange(1000))
for x in xrange(NUM_ITERATIONS):
np_array += x
def py_array_addition():
py_array = range(1000)
for x in xrange(NUM_ITERATIONS):
for i in xrange(len(py_array)):
py_array[i] += x
print timeit(np_array_addition, number=3) # 4.216162
print timeit(np_array_addition2, number=3) # 0.117681
print timeit(py_array_addition, number=3) # 0.439957
你可以看到,使用numpy的向量化版本在性能上明显更好。随着数组大小或循环次数的增加,这个差距会变得更大。
这并不是说加法本身慢,而是访问每个元素的开销比较大,看看下面这个例子:
def np_array_assignment():
np_array = np.array([1, 2])
for x in xrange(NUM_ITERATIONS):
np_array[0] = 1
np_array[1] = 1
def py_array_assignment():
py_array = [1, 2]
for x in xrange(NUM_ITERATIONS):
py_array[0] = 1
py_array[1] = 1
timeit np_array_assignment()
10000 loops, best of 3: 178 us per loop
timeit py_array_assignment()
10000 loops, best of 3: 72.5 us per loop
Numpy在处理向量(矩阵)时非常快,尤其是当你一次性对整个结构进行操作时。可是,如果你一个一个元素地操作,就会变得很慢。
为了避免循环,建议使用numpy的函数,这样可以一次性对整个数组进行操作,也就是说:
def np_array_addition_good():
np_array = np.array([1, 2])
np_array += np.sum(np.arange(NUM_ITERATIONS))
比较你自己的函数和上面提到的函数,结果会很明显:
timeit np_array_addition()
1000 loops, best of 3: 1.32 ms per loop
timeit py_array_addition()
10000 loops, best of 3: 101 us per loop
timeit np_array_addition_good()
100000 loops, best of 3: 11 us per loop
其实,如果你能把循环合并起来,使用纯Python也能做到同样的效果:
def py_array_addition_good():
py_array = [1, 2]
rangesum = sum(range(NUM_ITERATIONS))
py_array = [x + rangesum for x in py_array]
timeit py_array_addition_good()
100000 loops, best of 3: 11 us per loop
总的来说,对于这么简单的操作,使用numpy并没有太大优势。优化后的纯Python代码效果也很好。
关于这个问题有很多讨论,我建议你去看看一些好的回答: