【问题标题】:Why numpy arrays are slower than lists with for loops?为什么 numpy 数组比带有 for 循环的列表慢?
【发布时间】:2020-12-31 08:32:20
【问题描述】:

数组不应该更快,因为它们消耗更少的内存 并且据我所知,python 不会像在列表中那样对元素应用类型方法。

import numpy as np
import time


length = 150000000

my_list = range(length)


list_start_time = time.time()


for item in my_list:
    pass

print(f'my_list finished in: {time.time() - list_start_time}')
# # Output => my_list finished in: 3.57804799079895

my_array = np.arange(length)

array_start_time = time.time()


for item in my_array:
    pass

print(f'my_array finished in: {time.time() - array_start_time}')
# # Output => my_array finished in: 11.598113536834717

【问题讨论】:

  • 如果您使用矢量化(读取 Numpy)函数,它们会更快。使用常规循环需要在 C 库和 Python 运行时之间进行多次切换。简而言之,如果您使用常规循环,请不要使用 Numpy 数组。如果你只能使用 Numpy 函数,它们会更快。
  • 啊哈明白了,有没有对应for循环的numpy函数??
  • 不,这就是“矢量化”的意义所在。你到底想做什么?乘向量?只需* 即可。添加它们?同样...等等。
  • 我正在尝试添加它们,我可以看到它现在更快了,非常感谢:)

标签: python numpy


【解决方案1】:

my_list = range(length) 是一个range 对象,与其说是列表,不如说是一个生成器

在循环中:

 for i in range(10):
      pass

没有显着的内存使用。但即使我们确实迭代了一个列表,每个i 也只是对列表中一个项目的引用。实际上是一个简单的指针。该列表有一个数据缓冲区,其中包含指向内存中其他位置的对象的指针。迭代只需要获取这些指针,无需创建或处理任何对象。

arr = np.arange(10) 中,arr 是一个数组对象,带有一个包含表示整数值的字节的日期缓冲区,每个项目 8 个字节(在默认 dtype 中)。

 for i in arr:
      pass

numpy 索引每个元素,获取相关的 8 个字节(相对较快),并将它们转换为数字。整个过程比简单地从列表的数据缓冲区中获取引用更复杂。此过程有时称为“拆箱”。

为了说明,从该列表中创建 alist 和数组:

In [4]: alist = list(range(1000))
In [5]: arr = np.array(alist)

索引列表返回一个pythonint对象;从数组中我们得到一个numpy 对象:

In [6]: type(alist[0])
Out[6]: int
In [7]: type(arr[0])
Out[7]: numpy.int64

一些时间安排:

In [8]: timeit [i for i in alist]
27.9 µs ± 889 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [9]: timeit [i for i in arr]
124 µs ± 625 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

列表上的迭代要快得多(如您所述);并且基于以下时间,看起来数组迭代有效地执行了[i for i in list(arr)]

In [10]: timeit list(arr)
98 µs ± 661 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

tolist 方法将数组转换为列表,一直向下(到本机元素),并且速度更快。 [i for i in arr.tolist()] 实际上会节省时间。

In [11]: timeit arr.tolist()
22.8 µs ± 28 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

另一种说明“拆箱”的方法是查看同一元素的id(注意避免内存重用):

In [13]: x, y = alist[10], alist[10]; id(x), id(y)
Out[13]: (10914784, 10914784)
In [14]: x, y = arr[10], arr[10]; id(x), id(y)
Out[14]: (140147220887808, 140147220887832)

每次我们索引一个列表元素时,我们都会得到相同的id,相同的对象。

每次我们索引一个数组元素,我们都会得到一个新对象。创建对象需要时间。

numpy 数组更快 - 如果我们这样做迭代编译 c 代码。

例如给数组或列表的每个元素加100:

In [17]: timeit arr + 100
3.46 µs ± 136 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [18]: timeit [i+100 for i in alist]
60.1 µs ± 125 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

【讨论】:

    【解决方案2】:

    np.arrays 在使用矢量化函数时要快得多,或者换一种说法,特别是当您不迭代元素时。

    这是一个例子。我们将为您创建的有序列表中的每个数字加 1。

    设置类似,只是我使用列表而不是范围,因此我可以对值进行操作。

    length = 150000000
    my_list = list(range(length))
    my_array = np.array(my_list)
    

    在 jupyter notebooks 中使用 %%timeit 魔法函数

    %%timeit
    for i in range(len(my_list)):
        my_list[i] += 1
    

    给予

    38.6 s ± 3.71 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
    

    每个循环大约需要 38 秒

    另一方面

    %%timeit
    new_array = my_array + 1
    

    给予

    772 ms ± 58.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    

    对 numpy 数组的操作每次循环花费不到一秒(约 772 毫秒),同时遍历列表并向每个元素添加一个,每次循环大约 36 秒。

    【讨论】:

    • 谢谢你,你不需要 range(len(my_list)) 只是 my_list
    • timeit 对列表进行 7 次迭代,每次遍历的每个值都加 1。在 my_list 中使用 for i 将在第一次迭代中起作用,但在随后的迭代中,索引将关闭,给出“列表索引超出范围”
    猜你喜欢
    • 2017-11-09
    • 2018-05-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-17
    • 1970-01-01
    • 1970-01-01
    • 2019-12-08
    相关资源
    最近更新 更多