我们可以做一些调查来解决这个问题:
>>> 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.float64 或 numpy.int64 在这种情况下)包装返回的对象,如果您逐项迭代,这需要时间1。迭代时进一步证明了这一点——我们看到我们在迭代数组时在 2 个单独的 ID 之间交替。这意味着python的内存分配器和垃圾收集器正在超时工作以创建新对象然后释放它们。
list 没有这种内存分配器/垃圾收集器开销。列表中的对象已经作为 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
最后,您的 timeit 还包括不理想的列表/数组构建时间:
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 实际上离列表解决方案更远了。这表明 iteration 确实 较慢,如果将 numpy 类型转换为标准 python 类型,您可能会获得一些加速。
1注意,切片时这不会花费很多时间,因为只需要分配 O(1) 个新对象,因为 numpy 返回一个 view 进入原始数组。