【发布时间】:2021-05-13 16:18:05
【问题描述】:
我已经阅读了其他posts,关于 python 速度/性能应该如何相对不受正在运行的代码只是在 main 中、在函数中还是定义为类属性的影响,但这些并不能解释非常大的差异我在使用类与局部变量时看到的性能,尤其是在使用 numpy 库时。为了更清楚,我在下面做了一个脚本示例。
import numpy as np
import copy
class Test:
def __init__(self, n, m):
self.X = np.random.rand(n,n,m)
self.Y = np.random.rand(n,n,m)
self.Z = np.random.rand(n,n,m)
def matmul1(self):
self.A = np.zeros(self.X.shape)
for i in range(self.X.shape[2]):
self.A[:,:,i] = self.X[:,:,i] @ self.Y[:,:,i] @ self.Z[:,:,i]
return
def matmul2(self):
self.A = np.zeros(self.X.shape)
for i in range(self.X.shape[2]):
x = copy.deepcopy(self.X[:,:,i])
y = copy.deepcopy(self.Y[:,:,i])
z = copy.deepcopy(self.Z[:,:,i])
self.A[:,:,i] = x @ y @ z
return
t1 = Test(300,100)
%%timeit
t1.matmul1()
#OUTPUT: 20.9 s ± 1.37 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
%%timeit
t1.matmul2()
#OUTPUT: 516 ms ± 6.49 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
在这个脚本中,我将一个具有 X、Y 和 Z 属性的类定义为 3 路数组。我还有两个函数属性(matmul1 和 matmul2),它们循环遍历数组的第三个索引,矩阵乘以 3 个切片中的每一个来填充数组,A. matmul1 只是循环遍历类变量和矩阵乘法,而 matmul2 创建本地副本对于循环中的每个矩阵乘法。 Matmul1 比 matmul2 慢约 40 倍。有人可以解释为什么会这样吗?也许我正在考虑如何错误地使用类,但我也不认为变量应该一直被深度复制。基本上,深度复制对我的性能影响如此之大的原因是什么,并且在使用类属性/变量时这是不可避免的吗?它似乎不仅仅是调用类属性的开销here。任何意见表示赞赏,谢谢!
编辑:我真正的问题是,为什么类实例变量的子数组的副本而不是视图会为这些类型的方法带来更好的性能。
【问题讨论】:
-
如果我解释正确,你想知道为什么深拷贝版本要快得多(上面的评论遗漏了什么?)。这需要分析和更多的分析步骤。但是有两个明显的候选者: A:matmul2 indexed 允许 numpy 委托给 BLAS,而在 matmul1 中它以某种方式失败了。可能是?也许不吧。 B:索引深拷贝 O(n^2) 在执行一些 O(n^3) 操作之前确实会更改 存储顺序。由于缓存阻塞(现代 CPU),这有时会更快。两者都是可能是错误的猜测。这不是你肯定的假设。分析将显示时间花在代数上。
-
@juanpa.arrivillaga 令人困惑的是,执行这种昂贵的操作似乎会导致整体操作更快。
-
x=self.X[:,:,i].copy()就足够了。deepcopy仅在数组dtype是 `object.并不是说这会影响您的时间安排。 -
我认为这与“self.X[:,:,i]”不返回self.X中这些项目的副本有关,它返回一个“视图目的”。然后,您正在对两个视图对象执行 matmul。我不知道为什么这通常会比 matmul 慢得多。也许这是一个错误?
-
np.dot的速度与您的matmul2相似,即使没有副本也是如此。看起来matmul正在采取一些次优路线,数组是视图,无论是通过索引还是转置生成。
标签: python performance numpy class matrix-multiplication