您好, 欢迎来到 !    登录 | 注册 | | 设为首页 | 收藏本站

为什么在Python列表上执行“ for”要比在Numpy数组上执行“ for”更快?

为什么在Python列表上执行“ for”要比在Numpy数组上执行“ for”更快?

我们可以花点时间找出答案:

>>> import numpy as np
>>> a = np.arange(32)
>>> a
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
       17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31])
>>> a.data
<read-write buffer for 0x107d01e40, size 256, offset 0 at 0x107d199b0>
>>> id(a.data)
4433424176
>>> id(a[0])
4424950096
>>> id(a[1])
4424950096
>>> for item in a:
...   print id(item)
... 
4424950096
4424950120
4424950096
4424950120
4424950096
4424950120
4424950096
4424950120
4424950096
4424950120
4424950096
4424950120
4424950096
4424950120
4424950096
4424950120
4424950096
4424950120
4424950096
4424950120
4424950096
4424950120
4424950096
4424950120
4424950096
4424950120
4424950096
4424950120
4424950096
4424950120
4424950096
4424950120

那么这是怎么回事?首先,我看了数组的内存缓冲区的内存位置。在4433424176。这本身并 不太有 启发性。但是,numpy将其数据存储为连续的C数组,因此numpy数组中的第一个元素 应对 应于数组本身的内存地址,但不这样:

>>> id(a[0])
4424950096

但这不是一件好事,因为那会破坏python中的不变性,即2个对象id在其生命周期内永远不会具有相同的对象。

那么,numpy如何做到这一点?好吧,答案是numpy必须将返回的对象包装为python类型(例如numpy.float64numpy.int64在这种情况下为),如果您逐项迭代1,则会花费时间。迭代时将进一步证明这一点- 我们看到,在数组上进行迭代时,我们在2个独立的ID之间交替。这意味着python的内存分配器和垃圾收集器正在超时工作以创建新对象,然后释放它们。

一个 名单 没有这个内存分配器/垃圾收集开销。列表中的对象已经作为python对象存在(并且它们在迭代后仍将存在),因此在列表的迭代中都没有任何作用。

另请注意,您的时间安排会因您的假设而有所偏离。您以为k + 1在两种情况下这应该花费相同的时间,但是事实并非如此。请注意,如果我重复您的时间 没有 做任何加法:

mgilson$ python -m timeit -s "import numpy" "for k in numpy.arange(5000): k"
1000 loops, best of 3: 233 usec per loop
mgilson$ python -m timeit "for k in range(5000): k"
10000 loops, best of 3: 114 usec per loop

只有大约2倍的差异。但是,执行加法运算会导致相差约5倍:

mgilson$ python -m timeit "for k in range(5000): k+1"
10000 loops, best of 3: 179 usec per loop
mgilson$ python -m timeit -s "import numpy" "for k in numpy.arange(5000): k+1"
1000 loops, best of 3: 786 usec per loop

为了好玩,让我们做一下添加

$ python -m timeit -s "v = 1" "v + 1"
10000000 loops, best of 3: 0.0261 usec per loop
mgilson$ python -m timeit -s "import numpy; v = numpy.int64(1)" "v + 1"
10000000 loops, best of 3: 0.121 usec per loop

最后,您的时间还包括不理想的列表/数组构建时间:

mgilson$ python -m timeit -s "v = range(5000)" "for k in v: k"
10000 loops, best of 3: 80.2 usec per loop
mgilson$ python -m timeit -s "import numpy; v = numpy.arange(5000)" "for k in v: k"
1000 loops, best of 3: 237 usec per loop

注意,在这种情况下,numpy实际上离列表解决方案还很远。这表明 迭代 确实 慢一些,如果将numpy类型转换为标准python类型,则可能会加快速度。

1注意,切片时不会花费很多时间,因为那只需要分配O(1)新对象,因为numpy将 视图 返回到原始数组。

python 2022/1/1 18:33:53 有210人围观

撰写回答


你尚未登录,登录后可以

和开发者交流问题的细节

关注并接收问题和回答的更新提醒

参与内容的编辑和改进,让解决方法与时俱进

请先登录

推荐问题


联系我
置顶