【发布时间】:2021-07-23 22:47:46
【问题描述】:
给定一个方阵,我想将每一行按其行号移动并对列求和。例如:
array([[0, 1, 2], array([[0, 1, 2],
[3, 4, 5], -> [3, 4, 5], -> array([0, 1+3, 2+4+6, 5+7, 8]) = array([0, 4, 12, 12, 8])
[6, 7, 8]]) [6, 7, 8]])
我有 4 个解决方案 - fast、slow、slower 和 slowest,它们的作用完全相同,并且按速度排名:
def fast(A):
n = A.shape[0]
retval = np.zeros(2*n-1)
for i in range(n):
retval[i:(i+n)] += A[i, :]
return retval
def slow(A):
n = A.shape[0]
indices = np.arange(n)
indices = indices + indices[:,None]
return np.bincount(indices.ravel(), A.ravel())
def slower(A):
r, _ = A.shape
retval = np.c_[A, np.zeros((r, r), dtype=A.dtype)].ravel()[:-r].reshape(r, -1)
return retval.sum(0)
def slowest(A):
n = A.shape[0]
retval = np.zeros(2*n-1)
indices = np.arange(n)
indices = indices + indices[:,None]
np.add.at(retval, indices, A)
return retval
令人惊讶的是,非矢量化解决方案是最快的。这是我的基准:
A = np.random.randn(1000,1000)
%timeit fast(A)
# 1.85 ms ± 20 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit slow(A)
# 3.28 ms ± 9.55 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit slower(A)
# 4.07 ms ± 18.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit slowest(A)
# 58.4 ms ± 993 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
是否存在更快的解决方案?如果没有,有人可以解释为什么事实上fast 是最快的吗?
编辑
slow 略有加速:
def slow(A):
n = A.shape[0]
indices = np.arange(2*n-1)
indices = np.lib.stride_tricks.as_strided(indices, A.shape, (8,8))
return np.bincount(indices.ravel(), A.ravel())
以与 Pierre 相同的方式绘制运行时(以 2**15 作为上限 - 由于某种原因 slow 无法处理此大小)
对于100x100 的数组,slow 比任何解决方案(不使用numba)都要快一点。 sum_antidiagonals 仍然是 1000x1000 数组的最佳选择。
【问题讨论】:
-
注意到通过使用
np.lib.stride_tricks.as_strided生成笛卡尔索引,我可以将slow缩短约0.87 毫秒。 -
关于 why
fast()是最快的:使用切片作为左值非常聪明,并且使用numpy非常快。除了 Pythonfor循环之外,整个函数几乎完成了所需的最少操作量(r*c添加,或您的术语中的n^2);for循环的成本仅为O(n)(时间常数很小),与添加的O(n^2)相比变得可以忽略不计。因此fast()渐近地达到just_sum_0()的速度,这必须是O(n^2)加法运算的最快速度。
标签: python arrays numpy optimization vectorization