【问题标题】:Unexpected performance in Numpy from multiplying by 1Numpy 中的意外性能乘以 1
【发布时间】:2021-08-05 04:02:40
【问题描述】:

在 Windows 上的 Python 3.7.9 64 位和 Numpy 1.19.5 上测试

这是一个非常简单但令人困惑的问题。

考虑一下我给自己弄了一大堆形状(10000, 16)

import time
import numpy as np

arr = np.random.random((10000, 16))

现在我想将每一行与其他每一行的点积相乘。为此,我将数组乘以自身转置。结果数组的大小为(10000, 10000)。这是一项非常昂贵的操作,我不希望它很快。让我们计时吧。

def measure(func):
    start = time.time()
    func()
    print(time.time() - start)
>>> measure(lambda: arr * arr.T)
0.875575065612793

这里没有惊喜。与 Numpy 一样高性能,它仍然需要将近一秒钟的时间来计算结果。

但是如果...

>>> measure(lambda: arr * 1 @ arr.T)
0.4331023693084717

在执行矩阵乘法之前将矩阵乘以1 以某种方式加快了计算速度。

根据测试,如果arr 是其他数据类型,这也成立。

>>> arr = arr.astype('float32')
>>> measure(lambda: arr @ arr.T)
0.6592690944671631
>>> measure(lambda: arr * 1 @ arr.T)
0.22941327095031738

我们可以看到他们确实在计算相同的结果。

>>> np.max(np.abs(arr @ arr.T - arr * 1 @ arr.T))
1.9073486e-06

将数组乘以1(或任何其他标量)是否会给它一些超能力?我们可以测试一下。

>>> arr_times_1 = arr * 1
>>> measure(lambda: arr_times_1 @ arr.T)
0.23055601119995117

看起来确实如此。它会以某种方式改变数组吗? (答案是否定的。)

>>> np.max(np.abs(arr - arr_times_1))
0.0

我们能不能“抓住”这个超级大国?

>>> arr_copy_1 = arr_times_1.copy()
>>> arr_copy_2 = np.array(arr_times_1)
>>> measure(lambda: arr_copy_1 @ arr.T)
0.2252507209777832
>>> measure(lambda: arr_copy_2 @ arr.T)
0.22612690925598145

似乎我们可以。那么np.random.random 给我们的数组有问题吗?

>>> arr_copy_3 = np.array(arr)
>>> measure(lambda: arr_copy_2 @ arr.T)
0.2222919464111328

这个结果肯定支持这个理论。

>>> arr_copy_4 = arr.copy()
>>> measure(lambda: arr_copy_4 @ arr.T)
0.23076415061950684

即使只是在原始数组上调用copy() 似乎也能解决问题。那么可能是什么问题呢?

>>> arr.flags
  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : True
  WRITEABLE : True
  ALIGNED : True
  WRITEBACKIFCOPY : False
  UPDATEIFCOPY : False

>>> arr_times_1.flags
  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : True
  WRITEABLE : True
  ALIGNED : True
  WRITEBACKIFCOPY : False
  UPDATEIFCOPY : False

二进制数据有问题?

>>> arr_bytes = arr.tobytes()
>>> arr_times_1_bytes = arr_times_1.tobytes()
>>> arr_bytes == arr_times_1_bytes
True

没有区别。

为什么?

【问题讨论】:

    标签: python performance numpy


    【解决方案1】:

    原来速度变慢是因为被相乘的数组共享相同的内存,并且将数组乘以 1 在内存中创建了一个单独的数组。

    如果我们这样做

    >>> measure(lambda: arr_times_1 @ arr_times_1.T)
    0.6356322765350342
    

    我们再次观察退化的情况。因此,诀窍是简单地

    >>> measure(lambda: arr.copy() @ arr.T)
    0.2263638973236084
    

    【讨论】:

    • 但如果arr 更方正(例如(1000,1000)),arr@arr.T 更快。在其他 SO 中,我们发现matmul 可以选择使用一种替代计算,该计算据称利用了转置的共享内存。但显然,对于您非常矩形的阵列,这不是最佳选择。
    • 我还没有找到这个其他 SO 问题。可以链接吗?
    【解决方案2】:

    不是答案

    非常有趣的问题,在 linux 上的行为相同

    #py38 np.__version__ == 1.18.4
    
    
    %timeit arr @ arr.T
    948 ms ± 62.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    
    %timeit arr*1 @ arr.T
    707 ms ± 54.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    
    %timeit  arr.copy()  @ arr.T
    668 ms ± 22.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    
    %timeit  arr  @ arr.T
    945 ms ± 73.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    

    【讨论】:

      猜你喜欢
      • 2018-01-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-22
      • 2019-07-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多