所以我没有讲一个很长的故事,我正在编写一些代码,我从二进制文件中读取一些数据,然后使用for循环遍历每一个点。所以我完成了代码,它的运行速度非常慢。我从大约128个数据通道循环了大约60,000个点,这需要一分钟或更长时间来处理。这比我预期的Python运行速度要慢。所以我通过使用Numpy使整个事情变得更有效率但是在试图弄清楚为什么原始进程运行得如此之慢以至于我们进行了一些类型检查并发现我在循环Numpy数组而不是Python列表。好吧没有什么大不了的事情让我们的测试设置输入相同我在循环之前将Numpy数组转换为列表。轰炸相同的慢速代码,花了一分钟才能运行,现在耗时10秒。我被淹没了。我唯一的想法是将Numpy数组更改为Python列表我将其更改回来并且它再次变得很慢。我无法相信,所以我得到了更明确的证据
$ python -m timeit -s "import numpy" "for k in numpy.arange(5000): k+1"
100 loops, best of 3: 5.46 msec per loop
$ python -m timeit "for k in range(5000): k+1"
1000 loops, best of 3: 256 usec per loop
发生了什么事?我知道Numpy数组和Python列表是不同的,但为什么迭代数组中的每个点都要慢得多呢?
我在运行Numpy 10.1的Python 2.6和2.7中都观察到了这种行为。我相信。
答案 0 :(得分:16)
我们可以做一点调查来解决这个问题:
>>> 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
),如果你逐项迭代,这需要时间SUP> 1 。迭代时证明了这一点的进一步证明 - 我们看到我们在迭代数组时在两个独立的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
最后,你的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实际上远离了列表解决方案。这表明迭代实际 更慢,如果将numpy类型转换为标准python类型,可能会获得一些加速。
1 注意,切片时不需要花费很多时间,因为只需要分配O(1)个新对象,因为numpy返回视图< / em>进入原始数组。
答案 1 :(得分:0)
使用python 2.7
以下是我的速度以及xrange:
python -m timeit -s "import numpy" "for k in numpy.arange(5000): k+1"
1000次循环,最佳3次:每次循环1.22毫秒
python -m timeit "for k in range(5000): k+1"
10000次循环,最佳3:186次循环使用
python -m timeit "for k in xrange(5000): k+1"
10000次循环,最佳3:161次循环
Numpy明显变慢,因为它正在迭代一个特定于numpy的数组。这不是它的主要功能。在许多情况下,它们应该更像是一个单一的数字集合而不是简单的列表/迭代。例如,如果我们有一个相当大的python数字列表,我们想要提升到第三种力量,我们可能会这样做:
python -m timeit "lst1 = [x for x in range(100000)];" "lst2 = map(lambda x: x**3, lst1)"
10次循环,每次循环最佳3:125毫秒
注意:lst1表示任意列表。我知道你可以在原来的lambda中通过对范围内的x做x ** 3加快速度,但是这与已经存在的列表有关,并且很可能不是顺序的。
无论如何,numpy意味着被视为一个数组:
python -m timeit -s "import numpy" "lst1 = numpy.arange(100000)" "lst2 = lst1**2"
10000次循环,每次循环最佳3:120次使用
假设你有两个任意值列表,每个值都要加在一起。在vanilla python中,您可以这样做:
python -m timeit -s "lst1 = [x for x in xrange(0, 10000, 2)]" "lst2 = [x for x in xrange(2, 10002, 2)]" "lst3 = [x*y for x,y in zip(lst1, lst2)]"
1000次循环,每次循环最佳3:736次使用
在Numpy:
python -m timeit -s "import numpy" "lst1 = numpy.arange(0, 10000, 2)" "lst2 = numpy.arange(2, 10002, 2)" "lst3 = lst1*lst2"
100000次循环,最佳3:10.9次循环使用
在最后两个例子中,NumPy成为明显的赢家。对于列表上的简单迭代,范围或xrange是完全足够的,但是您的示例没有考虑Numpy数组的真正目的。这是比较飞机和汽车;是的,飞机通常比他们打算做的更快,但试图飞到当地的超市是不明智的。