gpt4 book ai didi

类实例与本地(numpy)变量的 Python 性能

转载 作者:行者123 更新时间:2023-12-04 08:07:58 25 4
gpt4 key购买 nike

我读过其他 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 个索引,并对 3 个切片中的每一个进行矩阵乘法以填充数组,A. matmul1 只是循环遍历类变量和矩阵乘法,而 matmul2 创建本地副本对于循环中的每个矩阵乘法。 Matmul1 比 matmul2 慢约 40 倍。有人可以解释为什么会这样吗?也许我正在考虑如何错误地使用类,但我也不认为变量应该一直被深度复制。基本上,什么是深度复制对我的性能影响如此显着,在使用类属性/变量时这是不可避免的吗?似乎它不仅仅是调用类属性的开销,正如所讨论的 here .任何输入表示赞赏,谢谢!
编辑:我真正的问题是为什么复制类实例变量的子数组而不是 View 会为这些类型的方法带来更好的性能。

最佳答案

如果你把 m首先是维度,你可以不用迭代就做这个产品:

In [146]: X1,Y1,Z1 = X.transpose(2,0,1), Y.transpose(2,0,1), Z.transpose(2,0,1)
In [147]: A1 = X1@Y1@Z1
In [148]: np.allclose(A, A1.transpose(1,2,0))
Out[148]: True
然而有时,由于内存管理的复杂性,使用非常大的数组会比较慢。
可能值得测试
 A1[i] = X1[i] @ Y1[i] @ Z1[i]
其中迭代是在最外层的维度上。
我的计算机太小,无法对这些数组大小进行良好的计时。
编辑
我将这些替代方案添加到您的类(class)中,并使用较小的案例进行了测试:
In [67]: 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):
...: A = np.zeros(self.X.shape)
...: for i in range(self.X.shape[2]):
...: A[:,:,i] = self.X[:,:,i] @ self.Y[:,:,i] @ self.Z[:,:,i]
...: return A
...: def matmul2(self):
...: A = np.zeros(self.X.shape)
...: for i in range(self.X.shape[2]):
...: x = self.X[:,:,i].copy()
...: y = self.Y[:,:,i].copy()
...: z = self.Z[:,:,i].copy()
...: A[:,:,i] = x @ y @ z
...: return A
...: def matmul3(self):
...: x = self.X.transpose(2,0,1).copy()
...: y = self.Y.transpose(2,0,1).copy()
...: z = self.Z.transpose(2,0,1).copy()
...: return (x@y@z).transpose(1,2,0)
...: def matmul4(self):
...: x = self.X.transpose(2,0,1).copy()
...: y = self.Y.transpose(2,0,1).copy()
...: z = self.Z.transpose(2,0,1).copy()
...: A = np.zeros(x.shape)
...: for i in range(x.shape[0]):
...: A[i] = x[i]@y[i]@z[i]
...: return A.transpose(1,2,0)

In [68]: t1=Test(100,50)
In [69]: np.max(np.abs(t1.matmul2()-t1.matmul4()))
Out[69]: 0.0
In [70]: np.allclose(t1.matmul3(),t1.matmul2())
Out[70]: True
view迭代慢 10 倍:
In [71]: timeit t1.matmul1()
252 ms ± 424 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [72]: timeit t1.matmul2()
26 ms ± 475 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
添加的内容大致相同:
In [73]: timeit t1.matmul3()
30.8 ms ± 4.33 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [74]: timeit t1.matmul4()
27.3 ms ± 172 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
没有 copy() , transpose生成 View ,时间类似 matmul1 (250 毫秒)。
我的猜测是,对于“新鲜”副本, matmul能够通过引用将它们传递给最好的 BLAS 函数。带有 View ,如 matmul1 ,它必须采取某种较慢的路线。
但是如果我使用 dot而不是 matmul ,即使使用 matmul1,我也能获得更快的时间迭代。
In [77]: %%timeit
...: A = np.zeros(X.shape)
...: for i in range(X.shape[2]):
...: A[:,:,i] = X[:,:,i].dot(Y[:,:,i]).dot(Z[:,:,i])
25.2 ms ± 250 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
它确实看起来像 matmul with views 正在采取一些次优的计算选择。

关于类实例与本地(numpy)变量的 Python 性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66129074/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com