【问题标题】:Why is for loop on native python list faster than for loop on numpy array为什么本机python列表上的for循环比numpy数组上的for循环更快
【发布时间】:2018-05-29 06:15:29
【问题描述】:

我正在阅读在高性能 Python 中介绍 numpy 的章节,并在我自己的计算机上使用了代码。意外地,我用 for 循环运行了 numpy 版本,发现与原生 python 循环相比,结果出奇地慢。

代码的简化版本如下,我定义了一个二维数组 X 为 0,另一个二维数组 Y 为 1,然后将 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/缓存/内存如何在低级别工作,我不了解吗?非常感谢。

编辑:更改标题

【问题讨论】:

  • 这不是numpy循环,而只是带有numpy数组的普通python循环。
  • 编辑了标题。不管怎么称呼它,问题是为什么存在性能差距。
  • 三件事:1.) 双索引:对于列表列表,您只是引用现有列表和元素,对于二维数组,您正在为行和元素创建一个新对象。 2.) numpy __getitem__/__setitem__ 处理比它们的列表计数器部分更多和更复杂的参数类型 3.) (我不完全理解其中的区别,但它就在那里)list.__getitem__ 是一个内置数组。@987654327 @ 是一个方法包装器
  • 所以你认为非本地的东西会胜过本地的东西?在本地人擅长的事情上? ...好吧,缓存参数很不错,但我认为与每个值的工作量相比,它的影响很小。

标签: python performance numpy for-loop memory


【解决方案1】:

一个更简单的例子 - 列表与数组的列表推导:

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)

【讨论】:

    【解决方案2】:

    因为它们是涉及向 numpy 询问数据指针、在这些指针位置检索值、然后使用它们进行迭代的转换。 python 列表中的这些步骤要少一些。仅当 Numpy 可以在内部迭代或执行向量、矩阵数学然后返回答案或指向答案数组的指针时,才会注意到 Numpy 的速度增益。

    【讨论】:

      猜你喜欢
      • 2021-10-01
      • 2016-05-15
      • 2020-12-31
      • 1970-01-01
      • 2017-11-09
      • 2019-03-26
      • 1970-01-01
      相关资源
      最近更新 更多