MATLAB矩阵元素级乘法优化

3

我需要优化一段MATLAB代码。这段代码很简单,但它是一个计算单元的一部分,该计算单元会调用它8000次(没有冗余)(在实际情况下,该计算单元会被使用10-20K次)。整个MATLAB代码相当长且复杂(对于像我这样的物理学家来说),但是MATLAB分析器声称以下代码段几乎占用了近一半的运行时间!

代码本质上是将三个矩阵组(A、B、C)中的每个排列逐元素相乘,并加上一些加权值的总和。组A有一个矩阵,组B有4个矩阵,组C有7个矩阵。

我尝试了一些向量化技术*,但最多只能得到相同的运行时间。

使用MATLAB分析器,我检查了每行的总耗时(对于所有8000次调用)- 我把它们写在注释中。

for idx_b = 1:4

B_MAT=B_Container_Cell{idx_b};

for idx_c = 1:7

    C_MAT = C_Container_Cell{idx_b}(:,:,idx_c); % 60 sec

    ACB=A_MAT.*C_MAT.*B_MAT; % 20 sec

    Coeff_x = Coeff_x_Cell{idx_b}(p1,p2,idx_c,p3);
    Coeff_y = Coeff_y_Cell{idx_b}(p1,p2,idx_c,p3);
    Coeff_z = Coeff_z_Cell{idx_b}(p1,p2,idx_c,p3);

    Sum_x = Sum_x+Coeff_x.*ACB; % 15 sec
    Sum_y = Sum_y+Coeff_y.*ACB; % 15 sec
    Sum_z = Sum_z+Coeff_z.*ACB; % 15 sec

end

结束

一些先前的知识 -
A_MAT是1024x1024的复数矩阵,定义在循环外
B_MAT是1024x1024的双精度矩阵,本质上是稀疏的(只有0和1的值,其中1的比例占总元素的约5%)
C_MAT是1024x1024的复数矩阵

Sum_x / Sum_y / Sum_z已经正确初始化
Coeff_X / Coeff_y / Coeff_z是双精度标量
p1、p2、p3是参数(对于这个代码段是常量)

有人知道为什么最耗时的操作是变量赋值吗? (我试图跳过赋值,并直接用它的表达式替换C_MAT,但性能更差)


向量化尝试
我尝试的技术是使用cat、reshape和repmat创建3个巨大的二维矩阵,对这些矩阵进行逐元素相乘,然后将它们全部放在一起(通过reshape),并通过相关的维度求和。第一个矩阵是A重复了4*7=28次,第二个矩阵是4个B矩阵重复了7次,第三个矩阵是所有的C矩阵跨越了(=28个矩阵)。


示例输入
以下链接上的代码生成示例输入文件。使用这些变量(在我的电脑上)的运行时间约为0.38秒(原始代码+变量约为0.42秒,我认为差异在于真实的C单元容器非常大,因此提取需要更多时间)


4
.* 运算是可交换的(与 * 矩阵乘法不同),因此你可以在内部循环之外执行 A_MAT*B_MAT - Ben Voigt
@BenVoigt 这不会对性能产生太大影响。 - Alexander
此外,在这些循环之前,Sum_x是否为0?(Sum_ySum_z同理) - BillBokeey
2
由于您谈论性能,请提供一个完全可重现的情况(可能需要一些额外的代码来生成实际大小的输入变量及其相关属性)。当然,确保运行时间与这些输入完全匹配。如果您指出您期望它有多快,这也可以帮助人们看到是否值得尝试某些技巧。 - Dennis Jaheruddin
1
如果容器单元具有相同大小的矩阵,为什么不使用多维数组代替不真正支持向量化操作的单元格数组。因此,B_Container_Cell 可以被 1024x1024x4 数组替换,C_Container_Cell 可以被 1024x1024x7x4 替换,类似地,对于 Coeff_x_CellCoeff_y_CellCoeff_z_Cell,可以分别使用 4D 数组。这样行得通吗? - Divakar
显示剩余8条评论
1个回答

2
考虑到输入单元格数组中的数组大小相同,将输入存储为多维数组而不是单元格数组可能是一个更好的主意,以利用MATLAB的矢量化技术,在这种情况下,可以使用索引提取特定元素和矩阵乘法进行求和缩减。因此,在形成输入时,我们可以尝试形成与输入对应的多维数组:B_Container_CellC_Container_CellCoeff_x_CellCoeff_y_CellCoeff_z_Cell。现在,这些是带有B_Container_Cell包含2D数组和其余包含3D数组的1D单元格数组。因此,在使用多维数组时,它们将作为一个附加维度,即它们将分别成为3D4D数组。
为了模拟它们的多维数组格式,让我们使用cat将给定的单元格数组沿着它们的last+1维进行连接,像这样-
Bm = cat(3,B_Container_Cell{:});
Cm = cat(4,C_Container_Cell{:});

Cx = cat(4,Coeff_x_Cell{:});
Cy = cat(4,Coeff_y_Cell{:});
Cz = cat(4,Coeff_z_Cell{:});

最后,使用向量化的解决方案来使用这些多维数组并获取所需的输出 -

%// Get ACB across all iterations and reshaped into (Nx28) shaped array
Ar = reshape(bsxfun(@times,bsxfun(@times,Cm,permute(Bm,[1,2,4,3])),A_MAT),[],28);

%// Use matrix-multiplication to sum reduce sliced versions of Cx, Cy and
%// Cz, to get respectived summed outputs
sz = size(A_MAT); %// Output array size
Sum_x_out = reshape(Ar*reshape(Cx(p1,p2,:,:),[],1),sz);
Sum_y_out = reshape(Ar*reshape(Cy(p1,p2,:,:),[],1),sz);
Sum_z_out = reshape(Ar*reshape(Cz(p1,p2,:,:),[],1),sz);

请注意,似乎没有使用参数 p3
运行时测试结果(针对列出的样本输入)-
--------------------------------- With Original Approach
Elapsed time is 2.412417 seconds.
--------------------------------- With Proposed Approach
Elapsed time is 1.572035 seconds.

将矩阵存储在多维数组中节省了很多时间(即使使用双重循环结构)。我也会尝试向量化,虽然你当之无愧地赢得了悬赏(我还不能颁发奖励,直到设定悬赏后过去24小时)。 - Alexander
@Alexander 不用着急,确保它能够正常工作并且足够高效。另外,很想听听向量化方法的运行时间与双重循环结构的对比。 - Divakar
有趣的是,任何向量化版本都至少比原来慢20%。即使更改了变量结构以优化bsxfun的内存使用,它仍然比原来慢。这段代码本来是要双重循环的。 - Alexander
@Alexander 这里需要注意的一件重要的事情是,这个缩减只是在28个元素(4x7)之间进行的。因此,也许这并不足以弥补形成大数组Ar所花费的时间。 - Divakar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接