简体   繁体   English

为什么 numpy 数组比带有 for 循环的列表慢?

[英]Why numpy arrays are slower than lists with for loops?

Aren't arrays supposed to be faster since they consume less memory and as I know with arrays python doesn't apply type method on the elements as it in the lists.数组不是应该更快,因为它们消耗更少的内存,而且正如我所知道的,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

my_list = range(length) is a range object, more of a generator than a list my_list = range(length)是一个range对象,与其说是一个列表,不如说是一个生成器

In the loop:在循环:

 for i in range(10):
      pass

there's no significant memory use.没有显着的内存使用。 But even if we did iterate on a list, each i would just be a reference to an item in the list.但即使我们确实对列表进行了迭代,每个i也只是对列表中项目的引用。 In effect a simple pointer.实际上是一个简单的指针。 The list has a data buffer, which contains pointers to objects elsewhere in memory.该列表有一个数据缓冲区,其中包含指向内存中其他位置的对象的指针。 Iteration simply requires fetching those pointers, without any object creation or processing.迭代只需要获取这些指针,无需任何对象创建或处理。

In arr = np.arange(10) , arr is an array object with a datebuffer containing bytes representing the values of the integers, 8 bytes per item (in the default dtype).arr = np.arange(10)arr是一个数组对象,带有一个包含表示整数值的字节的日期缓冲区,每项 8 个字节(在默认 dtype 中)。

 for i in arr:
      pass

numpy indexes each element, fetching the relevant 8 bytes (relatively fast), and converting them to a number. numpy索引每个元素,获取相关的 8 个字节(相对较快),并将它们转换为数字。 The whole process is more involved than simply fetching a reference from a list's data buffer.整个过程比简单地从列表的数据缓冲区中获取引用更复杂。 This process is sometimes called 'unboxing'.这个过程有时被称为“拆箱”。

To illustrate, make alist and array from that list:为了说明,从该列表中创建 alist 和 array:

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

Indexing the list returns a python int object;索引列表返回一个 python int对象; from the array we get a numpy object:从数组中我们得到一个numpy对象:

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

Some timings:一些时间:

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)

iteration on the list is much faster (as you note);列表上的迭代要快得多(如您所见); and based on the following timing it looks like the array iteration effectively does [i for i in list(arr)] :并且基于以下时间,看起来数组迭代有效地执行[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)

The tolist method converts the array to a list, all the way down (to native elements), and is much faster. tolist方法将数组转换为列表,一直向下(到本地元素),并且速度要快得多。 [i for i in arr.tolist()] will actually save time. [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)

Another way to illustrate the 'unboxing' is to look at the id of the same element (taking care to avoid memory reuse):另一种说明“拆箱”的方法是查看同一元素的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)

Each time we index a list element, we get the same id , the same object.每次我们索引一个列表元素时,我们都会得到相同的id ,相同的对象。

Each time we index an array element, we get a new object.每次我们索引一个数组元素时,我们都会得到一个新对象。 That object creation takes time.创建对象需要时间。

numpy arrays are faster - if we do the iteration is compiled c code. numpy数组更快 - 如果我们进行迭代,则编译为c代码。

For example to add 100 to each element of the array or list:例如将 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)

np.arrays are much faster when using vectorized functions or to say it another way, specifically when you are not iterating over the elements. np.arrays 在使用矢量化函数或换种说法时要快得多,特别是当您不迭代元素时。

Here is an example.这是一个例子。 We will add 1 to each number in the ordered list you created.我们将为您创建的有序列表中的每个数字加 1。

The setup is similar except I use lists instead of ranges so I can operate on the values.设置是相似的,除了我使用列表而不是范围,以便我可以对值进行操作。

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

Using the %%timeit magic function in jupyter notebooks在 jupyter notebooks 中使用 %%timeit 魔法函数

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

gives

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

thats about 38 seconds per loop每个循环大约 38 秒

On the other hand另一方面

%%timeit
new_array = my_array + 1

gives

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

The operation on the numpy array took under one second (~772ms) per loop while iterating through the list and adding one to each element too around 36 seconds per loop.对 numpy 数组的操作每个循环花费不到 1 秒(~772 毫秒),同时遍历列表并向每个元素添加一个元素,每个循环大约 36 秒。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM