为什么numpy.absolute()这么慢?

Lug*_*ugi 7 optimization numpy addition absolute-value

我需要优化一个大量使用计算L1矢量规范的脚本.我们知道在这种情况下L1范数只是绝对值的总和.当计算numpy在这个任务中的速度有多快时,我发现了一些奇怪的东西:添加所有向量元素比获取向量的每个元素的绝对值快大约3倍.这是一个令人惊讶的结果,因为与采用绝对值相比,加法非常复杂,绝对值只需要对数据块的每第32位置零(假设为float32).

为什么这个加法比简单的按位运算快3倍?

import numpy as np

a = np.random.rand(10000000)

%timeit np.sum(a)
13.9 ms ± 87.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit np.abs(a)
41.2 ms ± 92.3 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
Run Code Online (Sandbox Code Playgroud)

小智 3

np.sum返回一个标量。np.abs返回一个相同大小的新数组。为新数组分配内存是这里花费最多时间的事情。比较

>>> timeit("np.abs(a)", "import numpy as np; a = np.random.rand(10000000)", number=100)
3.565487278989167
>>> timeit("np.abs(a, out=a)", "import numpy as np; a = np.random.rand(10000000)", number=100)
0.9392949139873963
Run Code Online (Sandbox Code Playgroud)

该参数out=a告诉 NumPy 将结果放入同一个数组中a,覆盖那里的旧数据。因此加速。

Sum 仍然稍微快一些:

>>> timeit("np.sum(a)", "import numpy as np; a = np.random.rand(10000000)", number=100)
0.6874654769926565
Run Code Online (Sandbox Code Playgroud)

但它不需要那么多的内存访问。

如果您不想覆盖 a,abs则可以为 a 的输出提供另一个数组,假设您必须重复获取abs相同类型和大小的数组。

b = np.empty_like(a)   # done once, outside the loop
np.abs(a, out=b)
np.sum(b)
Run Code Online (Sandbox Code Playgroud)

运行时间大约是np.linalg(a, 1)

作为参考,np.linalg将 L1 范数计算为

add.reduce(abs(x), axis=axis, keepdims=keepdims)
Run Code Online (Sandbox Code Playgroud)

这涉及到为新数组分配内存abs(x)


理想情况下,有一种方法可以计算所有绝对值(或另一个“ufunc”的结果)的总和(或最大值或最小值),而无需将所有输出移至 RAM,然后检索其求和/最大值/最小值。NumPy 存储库中进行了一些讨论,最近一次是添加 max_abs ufunc,但尚未实现。

ufunc.reduce方法适用于具有两个输入的函数,例如addlogaddexp,但没有可用于归约的 addabs函数 ( )。x, y : x+abs(y)