[英]Why is for loop on native python list faster than for loop on numpy array
我正在阅读介绍高性能Python中的 numpy并在我自己的计算机上使用代码的章节。 偶然地,我用for循环运行了numpy版本,发现与本地python循环相比,结果出奇地慢。
该代码的简化版本如下所示,其中我定义了一个0的2D数组X和另一个1的2D数组Y,然后将Y重复地添加到X,从概念上讲X + =Y。
import time
import numpy as np
grid_shape = (1024, 1024)
def simple_loop_comparison():
xmax, ymax = grid_shape
py_grid = [[0]*ymax for x in range(xmax)]
py_ones = [[1]*ymax for x in range(xmax)]
np_grid = np.zeros(grid_shape)
np_ones = np.ones(grid_shape)
def add_with_loop(grid, add_grid, xmax, ymax):
for x in range(xmax):
for y in range(ymax):
grid[x][y] += add_grid[x][y]
repeat = 20
start = time.time()
for i in range(repeat):
# native python: loop over 2D array
add_with_loop(py_grid, py_ones, xmax, ymax)
print('for loop with native list=', time.time()-start)
start = time.time()
for i in range(repeat):
# numpy: loop over 2D array
add_with_loop(np_grid, np_ones, xmax, ymax)
print('for loop with numpy array=', time.time()-start)
start = time.time()
for i in range(repeat):
# vectorized numpy operation
np_grid += np_ones
print('numpy vectorization=', time.time()-start)
if __name__ == "__main__":
simple_loop_comparison()
结果看起来像:
# when repeat=10
for loop with native list= 2.545672655105591
for loop with numpy array= 11.622980833053589
numpy vectorization= 0.020279645919799805
# when repeat=20
for loop with native list= 5.195128440856934
for loop with numpy array= 23.241904258728027
numpy vectorization= 0.04613637924194336
我完全期望numpy向量化操作的性能优于其他两个,但是我很惊讶地看到在numpy数组上使用for循环的结果比本地python列表要慢得多。 我的理解是,至少使用numpy数组,即使使用for循环,缓存也应相对较好地填充,并且在不进行向量化的情况下,其性能应优于列表。
关于numpy或我不了解的低级CPU /缓存/内存如何工作? 非常感谢你。
编辑:更改标题
一个更简单的情况-对列表和数组的列表理解:
In [119]: x = list(range(1000000))
In [120]: timeit [i for i in x]
47.4 ms ± 634 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [121]: arr = np.array(x)
In [122]: timeit [i for i in arr]
131 ms ± 3.69 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
列表具有一个数据缓冲区,该缓冲区包含指向内存中其他对象的指针。 因此,对列表进行迭代或索引编制仅需要查找该指针并获取对象:
In [123]: type(x[1000])
Out[123]: int
数组将其元素作为字节存储在数据缓冲区中。 提取元素需要(快速)找到那些字节,然后将它们包装在numpy对象中(根据dtype)。 这样的对象类似于0d单元素数组(具有许多相同的属性)。
In [124]: type(arr[1000])
Out[124]: numpy.int32
该索引不仅获取数字,而且还会重新创建它。
我经常将对象dtype数组描述为增强列表或降级列表。 像列表一样,它包含指向内存中其他位置的对象的指针,但不能通过append
增长。 我们经常说它失去了数字数组的许多好处。 但是它的迭代速度介于其他两个之间:
In [125]: arrO = np.array(x, dtype=object)
In [127]: type(arrO[1000])
Out[127]: int
In [128]: timeit [i for i in arrO]
74.5 ms ± 1.42 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
无论如何,我在其他SO答案中发现,如果必须迭代,请坚持使用列表。 而且,如果您从列表开始,那么坚持使用列表通常会更快。 如您numpy vector
速度很快,但是创建数组需要花费时间,这可能会抵消任何节省的时间。
比较从此列表创建数组所需的时间,与从头开始创建此类数组所需的时间(使用已编译的numpy代码):
In [129]: timeit np.array(x)
109 ms ± 1.97 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [130]: timeit np.arange(len(x))
1.77 ms ± 31.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
由于它们是涉及向numpy询问数据指针,在那些指针位置检索值,然后使用它们进行迭代的转换。 python列表包含较少的步骤。 只有在可以内部迭代或执行矢量,矩阵数学然后返回并返回答案或指向答案数组的指针时,才会注意到Numpy速度增益。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.