我有一个向量数组,并计算他们的差异与第一个的差异.使用python广播时,计算速度明显慢于通过简单循环进行计算.为什么?
import numpy as np
def norm_loop(M, v):
n = M.shape[0]
d = np.zeros(n)
for i in range(n):
d[i] = np.sum((M[i] - v)**2)
return d
def norm_bcast(M, v):
n = M.shape[0]
d = np.zeros(n)
d = np.sum((M - v)**2, axis=1)
return d
M = np.random.random_sample((1000, 10000))
v = M[0]
Run Code Online (Sandbox Code Playgroud)
%timeit norm_loop(M,v) - > 25.9 ms
%timeit norm_bcast(M,v) - > 38.5 ms
我有Python 3.6.3和Numpy 1.14.2
要在google colab中运行示例:https://drive.google.com/file/d/1GKzpLGSqz9eScHYFAuT8wJt4UIZ3ZTru/view ? usp = sharing
内存访问.
首先,广播版本可以简化为
def norm_bcast(M, v):
return np.sum((M - v)**2, axis=1)
Run Code Online (Sandbox Code Playgroud)
这仍然比循环版本略慢.现在,传统观念认为,使用广播量化代码应该永远是更快,这在很多情况下是不正确的(我无耻地插入我的另一个答案在这里).那么发生了什么?
正如我所说,它归结为内存访问.
在广播版本中,从v中减去M的每个元素.到处理M的最后一行时,处理第一行的结果已经从高速缓存中逐出,因此对于第二步,这些差异再次被加载到高速缓冲存储器中.平方.最后,它们被加载并第三次处理以进行求和.由于M非常大,因此在每一步都清除部分缓存以编写所有数据.
在循环版本中,每一行都在一个较小的步骤中完全处理,从而减少了缓存未命中和整体更快的代码.
最后,通过使用一些数组操作可以避免这种情况einsum.此功能允许混合矩阵乘法和求和.首先,我会指出它是一个与其他numpy相比具有相当不直观的语法的函数,并且潜在的改进通常不值得花费额外的努力去理解它.由于舍入误差,答案也可能略有不同.在这种情况下,它可以写成
def norm_einsum(M, v):
tmp = M-v
return np.einsum('ij,ij->i', tmp, tmp)
Run Code Online (Sandbox Code Playgroud)
这将它减少到整个数组上的两个操作 - 减法和调用einsum,它执行平方和求和.这略有改进:
%timeit norm_bcast(M, v)
30.1 ms ± 116 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit norm_loop(M, v)
25.1 ms ± 37.3 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit norm_einsum(M, v)
21.7 ms ± 65.3 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
533 次 |
| 最近记录: |