当前位置: 首页 > 面试题库 >

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

皇甫飞宇
2023-03-14
问题内容

因此,我没有讲一个很长的故事,而是在编写一些代码,即从二进制文件中读取一些数据,然后使用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中都观察到了这种行为。


问题答案:

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

>>> 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将 视图 返回到原始数组。



 类似资料:
  • } 链接:https://www.hackerrank.com/challenges/java-string-compare/problem

  • 问题内容: 我试图更好地掌握numpy数组,所以我有一个关于它们的示例问题: 假设我有一个名为a的numpy数组。我想对进行操作,使其中的所有小于0的值递增,其余的保持不变。例如,如果我有: 我想返回: 最紧凑的语法是什么? 谢谢! 问题答案:

  • 问题内容: 是什么解释了列表和NumPy数组上布尔运算和按位运算的行为差异? 我&对and在Python中适当使用vs 感到困惑,如以下示例所示。 这个答案和这个答案帮助我理解这and是一个布尔运算,但是&按位运算。 我阅读了有关按位运算的信息,以更好地理解该概念,但是我正在努力使用该信息来理解我上面的四个示例。 示例4使我达到了期望的输出,这很好,但是对于何时/如何/为什么应该使用andvs 仍

  • 问题内容: 我正在尝试执行此命令 和 都不起作用(返回空白输出) 有人知道为什么吗? 问题答案: 因为top是一个交互式程序,旨在在终端上运行,而不是从脚本中执行。您可能需要运行带有参数的“ ps”命令,这些命令将按cpu利用率对输出进行排序。 http://www.devdaily.com/linux/unix-linux-process-memory-sort-ps-command- cpu

  • 问题内容: 我只是发现我认为PLSQL与Oracle中的SQL有点意外的行为。 如果我在SQLDeveloper上运行此查询,则会得到5个结果: 但是,如果我在SQLDeveloper中运行以下语句: 变量 w_counter 以值1(怪异)结束 但最奇怪的部分是,如果我将查询封装在子查询中…… 该 w_counter 变量完成与价值5 … 你对此有什么要说的? 我正在使用Oracle 9.2i