【问题标题】:使用“,”对切片 numpy 数组进行广播比较比“][”慢很多
【发布时间】:2022-01-23 09:56:31
【问题描述】:

我不确定为什么使用 , 比较切片 numpy 数组比 ][ 慢很多。例如:

start = time.time()
a = np.zeros((100,100))
for _ in range(1000000):
    a[1:99][1:99] == 1
print(time.time() - start)

start = time.time()
a = np.zeros((100,100))
for _ in range(1000000):
    a[1:99, 1:99] == 1
print(time.time() - start)
3.2756259441375732
11.044903039932251

这比这差了 3 倍多。 使用timeit 的时间测量值大致相同。

我正在研究递归算法(我打算这样做),这些问题使我的程序运行速度变慢了很多,从大约 1 秒增加到 10 秒。我只是想知道他们背后的原因。可能这是一个错误。我正在使用 Python 3.9.9。谢谢。

【问题讨论】:

  • @mozway。 OP 清楚地意识到这一点,这不是错别字
  • 不是很多快...
  • 如果你在没有any的情况下进行比较会怎样?
  • @NhậtMinh。谢谢。这很迷人。我会尝试做一些挖掘
  • @NhậtMinh。第二个如预期的那样,所以我会继续说我明白那里发生了什么。第一个仍然是个谜

标签: python arrays numpy slice


【解决方案1】:

第一个与a[2:99]==1相同。一个 (98,100) 切片,然后是一个 (97,100),然后是 == 测试。

In [177]: timeit (a[1:99][1:99]==1)
8.51 µs ± 16.3 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [178]: timeit (a[1:99][1:99])
383 ns ± 5.73 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [179]: timeit (a[1:99])
208 ns ± 10.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

大部分时间是测试,而不是切片。

In [180]: a[1:99,1:99].shape
Out[180]: (98, 98)
In [181]: timeit a[1:99,1:99]==1
32.2 µs ± 12.9 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [182]: timeit a[1:99,1:99]
301 ns ± 3.61 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

同样,切片只是时间的一小部分,但== 测试明显慢得多。在第一种情况下,我们选择了行的一个子集,因此测试是在数据缓冲区的一个连续块上进行的。在第二个中,我们选择行和列的子集。遍历数据缓冲区更复杂。

我们可以通过测试列切片与行切片来简化比较:

In [183]: timeit a[:,2:99]==1
32.3 µs ± 13.8 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [184]: timeit a[2:99,:]==1
8.58 µs ± 10.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

作为进一步的测试,使用“F”顺序创建一个新数组。现在“行”是慢切片

In [189]: b = np.array(a, order='F')
In [190]: timeit b[:,2:99]==1
8.83 µs ± 20.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [191]: timeit b[2:99,:]==1
32.8 µs ± 31.2 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

===

但是你为什么要比较这两个切片,一个是 (97,100) 数组,另一个是 (98,98)。他们正在挑选a 的不同部分。

我想知道您是否真的要测试连续的行、列切片,而不是两个行切片。

In [193]: timeit (a[1:99][:,1:99]==1)
32.6 µs ± 92.4 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

仅比较切片,我们发现顺序切片更慢 - 只是一点点。

In [194]: timeit (a[1:99][:,1:99])
472 ns ± 3.76 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [195]: timeit (a[1:99,1:99])
306 ns ± 3.19 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

===

a 的数据实际上存储在 1d c 数组中。 numpy 代码在执行 a[...] == 1 之类的操作时使用 strides 和 shape 对其进行迭代。

想象一下 (3,6) 数据缓冲区的样子

[0 1 2 3 4 5 0 1 2 3 4 5 0 1 2 3 4 5]

[1:3]切片,它将使用

[_ _ _ _ _ _ 0 1 2 3 4 5 0 1 2 3 4 5]

使用[:,1:4] 切片

[_ 1 2 3 _ _ _ 1 2 3 _ _ _ 1 2 3 _ _]

不管处理器缓存细节如何,通过第 2 次的迭代更加复杂。

【讨论】:

  • 我完全误读了原始问题中的片段。当您说行与列时,它变得很明显
  • 感谢您的帮助。我完全明白了。它。是的,这是我的错误。我会把这个问题留在这里,以防其他人和我犯同样的错误。
  • 除了我的错,关于顺序数组的事情真的让我很感兴趣。这就是为什么我需要专注于数据结构类,lol
  • @NhậtMinh,这更像是处理器架构的事情。通常,现代处理器不只是从 RAM 中读取您要求的位置,而是读取它周围的一整块。如果您的阵列是连续的,您最终能够处理整个缓存而无需频繁加载它。当您必须跳过元素(例如跨行移动)时,新的内存位置将不会被缓存,因此需要再次加载,这需要更多时间。这是一个非常非正式的解释,但它传达了总体思路。实际上通常有多个核心和缓存级别。
  • 我添加了几行来展示对某些列进行切片会导致更复杂的迭代模式。我们大多数人不需要确切地知道为什么迭代需要更多时间——很多事情在幕后进行,numpy 迭代机制、编译器优化、操作系统和处理器的内存处理等。它也可能是一个移动目标,取决于各种软件包的版本和您自己的计算机及其内存。
猜你喜欢
  • 2013-05-09
  • 1970-01-01
  • 2019-01-13
  • 2014-04-06
  • 1970-01-01
  • 1970-01-01
  • 2014-09-21
  • 2018-11-07
相关资源
最近更新 更多