Matlab子矩阵乘法性能差

3

在Matlab中,子矩阵的乘法速度似乎比它们所来自的矩阵的乘法要慢很多。

>> m = rand(1e7, 40);
>> tic; m' * m; toc % (1)
Elapsed time is 0.245803 seconds.
>> tic; m(1:2.5e6, :)' * m(1:2.5e6, :); toc % (2)
Elapsed time is 1.810981 seconds.
>> tic; t = m(1:2.5e6, :); t' * t; toc % (3)
Elapsed time is 0.885764 seconds.

我曾希望有一些快速的内置方法可以实现这一点,因为数据已经在内存中,但似乎没有办法阻止Matlab制作中间副本。 (3) 更快,但保证我们制作一个副本。
在Matlab中是否有任何技术可以使操作(例如乘法,转置)在矩阵子集上像整个矩阵一样快?
唯一快速实现这一点的方法是使用mex吗?
编辑:使用列主要数据可以加快整体速度(如预期),但在乘以子矩阵时仍然慢得多。
>> m = rand(40,1e7);
>> tic; m * m'; toc
Elapsed time is 0.251958 seconds.
>> tic; m(:, 1:2.5e6) * m(:, 1:2.5e6)'; toc
Elapsed time is 1.461321 seconds.
>> tic; s=m(:, 1:2.5e6); s * s'; toc
Elapsed time is 0.555667 seconds.

很明显,在索引时Matlab会进行复制,但是否有办法防止这种情况发生呢(很明显,这样的乘法算法可以在不复制数据的情况下存在,我只想知道它是否可在Matlab中表达)。

1
而且(2)保证我们制作了两份副本,我认为MATLAB解释器不够聪明,无法优化掉这个问题。两个变量可以共享数据,但只有它们完全相同(即a = b)时才能共享。也许如果子数组在内存中是连续的,也可以共享,但我从未尝试过。否则,索引总是会导致复制。请注意,您的索引涵盖了m的非连续区域。您是否尝试过使用m = rand(40,1e7)t = m(:,1:2.5e6)进行操作? - Cris Luengo
@CrisLuengo 我碰巧有行主数据,但我已经编辑了你的建议,因为存在相同的问题。 - cloakedlearning
1
如果你想加速这个过程,我猜你需要编写一个MEX文件。我不认为MATLAB有优化这个的方法。另一方面,你可能会在MATLAB的答案网站上找到不同的意见(这是所有MATLAB专家似乎都聚集的地方):https://www.mathworks.com/matlabcentral/answers/index - Cris Luengo
@CrisLuengo 我最终写了一个MEX文件,速度快了很多。如果你把你的评论变成答案,我会很高兴地接受它。 - cloakedlearning
请告诉我我发布的答案是否足够。 - Cris Luengo
2个回答

2

这里是更多的测试结果。

已经测试了五种矩阵乘法的方法,其中3种涉及子矩阵。正如@Cris所指出的,如果使用rand(1e7,40)可能会有差异,因此进行了另一组完整的测试。

使用10次循环和Profiler来提高准确性。在一台配备充足RAM的i7上进行测试。

测试代码

% clear;clc;close all

A = rand(1e7, 40);
for ii = 1:10
    m = A;
    m(end) = m(end-1);

    m' * m; % 1

    n = m';
    n * m; % 2

    m(1:2.5e6, :)' * m(1:2.5e6, :); % 3

    t = m(1:2.5e6, :);
    t' * t; % 4

    t2 = t';
    t2 * t; % 5

    clearvars -except A
end
clearvars A
B = rand(40, 1e7);
for ii = 1:10
    m = B;
    m(end) = m(end-1);

    m * m'; % 1

    n = m';
    m * n; % 2

    m(:,1:2.5e6) * m(:,1:2.5e6)'; % 3

    t = m(:,1:2.5e6);
    t * t'; % 4

    t2 = t';
    t * t2; % 5

    clearvars -except B
end

性能分析结果

每个方法的总时间

性能分析截图

注释

  1. 任何少于10%的时间差异都被认为是相同的。
  2. 在每次循环开始时,矩阵通过简单赋值强制进行复制。这种复制速度较慢(11.5秒)。
  3. 对于完整的矩阵,MxN和NxM维度之间没有区别。但是对于子矩阵测试,40x2.5e6 明显更快。
  4. 缓存转置会将整个过程减慢100%。我认为这是因为Matlab失去了优化的能力;可能如果一个人键入 m'*m,它会识别出模式并跳过转置操作。
  5. 子集和转置不是经过优化的。
  6. 缓存子集确实加速了乘法运算。

感谢您提供详细的答案。从某种意义上说,我仍然没有一个好的答案来回答这个问题——为什么在子集上进行乘法比在整个集合上进行乘法要慢得多。我猜测复制操作占主导地位,而且没有办法消除复制操作。 - cloakedlearning
@cloakedlearning 我同意。我的代码中第36行和第15行显示了复制子集的时间。 - Yvon

1
MATLAB使用写时复制,这意味着复制矩阵并不真正复制数据,至少在您尝试更改指向相同数据的两个矩阵中的一个之前是这样。但是,当您复制矩阵的一部分时,数据总是会被复制。也就是说,矩阵永远不会指向不同矩阵子集的值。MATLAB始终按顺序(列主序)在内存中存储数据,因此索引的矩阵部分很少连续存储在内存中。尝试此操作的一种方法是使用format debug并查看数据指针:
>> format debug
>> q=rand(10,100)
q =
Structure address = 127e64560
m = 10
n = 100
pr = 7f9f5c835420
pi = 0
  Columns 1 through 9
    0.8147    0.1576    0.6557    0.7060    0.4387    0.2760    0.7513    0.8407    0.3517
    ...

>> q(:,1:2)
ans =
Structure address = 11dadd750
m = 10
n = 2
pr = 7f9f5b792700
pi = 0
  0.8147    0.1576
  ..

注意观察 pr 值(指向数据实值部分的指针)的变化。这表明数据被复制了。
如果无法使用向量化代码(即将操作作为整体应用于矩阵),加速计算的最佳选择是使用 MEX 文件。

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