一个更简单的情况 - 列表与数组的列表理解:
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
此索引不仅获取数字,还重新创建数字。
我经常将对象数据类型数组描述为增强或降级列表。与列表一样,它包含指向内存中其他位置的对象的指针,但它不能按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)
无论如何,我在其他答案中发现,如果必须迭代,请坚持使用列表。如果您从列表开始,那么坚持使用列表通常会更快。正如您注意到的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)