Numpy数组索引和/或加法似乎很慢

3 投票
2 回答
6786 浏览
提问于 2025-04-17 21:13

我在玩弄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 个回答

4

如果你在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的向量化版本在性能上明显更好。随着数组大小或循环次数的增加,这个差距会变得更大。

5

这并不是说加法本身慢,而是访问每个元素的开销比较大,看看下面这个例子:

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代码效果也很好。


关于这个问题有很多讨论,我建议你去看看一些好的回答:

如何最大化numpy数组的效率?

numpy浮点数:在算术操作中比内置函数慢10倍?

撰写回答