【问题标题】:Is it possible to make this vectorized MATLAB code faster?是否有可能使这个向量化的 MATLAB 代码更快?
【发布时间】:2012-09-14 02:15:50
【问题描述】:

在我的程序中,我需要计算总和:

.

我用 Cz 的新值计算了这个总和 2500 次。

参数z 可能是一个向量。我写了简单的for循环和矢量化版本代码如下:

K = 200;
n_z = 40000;
C = ones(K,1); % an example, in real life the arey some coefficients (at next call will be new)
k = 0:K-1;
z = linspace(0, 2*pi, n_z); % at next call will be new

tic;
    my_sum_for = zeros(1, K);
    for i=1:n_z
       my_sum_for(i) = C' * tan(k' * z(i));
    end
toc; % Elapsed time is 1.820485 seconds.

tic;
     my_sum = C' * tan(k' * z);
toc; % Elapsed time is 0.160924 seconds.

矢量化版本更快,但还不够。 是否可以改进矢量化版本?

在 Dominique Jacquel 的回答之后,我有了这个矢量化版本,它更快:

    K = 200;
    n_z = 40000;
    C = ones(K,1)';  % an example, in real life they are some coefficients (at next call will be new)
    k = (0:K-1)';
    z = linspace(0, 2*pi, n_z);  % at next call will be new

    tic;
        my_sum_for = zeros(1, K);
        for i=1:n_z
           my_sum_for(i) = C * tan(k * z(i));
        end
    toc; % Elapsed time is 1.521587 seconds.

    tic;
         my_sum = C * tan(k * z);
    toc; % Elapsed time is 0.125468 seconds.

是否有可能进一步改进矢量化版本(bsxfun、arrayfun 之类的)? 250 秒的时间对我来说仍然很慢(占所有计算的 75%)。

【问题讨论】:

  • all(my_sum_for == my_sum) -> ans = 0 ...所以,您还没有检查过这个吗?在我看来,你做了一些不需要的奇怪转调..,
  • 我通过计算 norm(my_sum_for, my_sum) = 1.7861e-10 进行了检查。对我来说,相同代码的矢量化版本和循环版本会产生略有不同的结果,这对我来说并不新鲜。
  • C的实际内容真的只是一个吗?并且矩阵 k*z 中有一些重复的元素,您可能可以跳过计算 tan 的值,但考虑到 Kn_Z 的值,那里没有什么可保存的。
  • 这是一个例子,C是一些系数的数组。最新版本的内存消耗最小。可能有更快的解决方案和更多的内存消耗?我的程序调用这个代码 2500 次,所以它是 250 秒。我希望我们可以更快地编写代码。
  • 对这段代码的 2500 次调用之间有什么变化?这段代码是否总是使用Cz 的新值调用?如果没有,您可能会利用它来进一步优化。

标签: matlab vectorization


【解决方案1】:

我认为您已经非常接近这里的硬件限制了。 Matlab 中的矩阵乘法是使用BLAS libraries 完成的,在性能方面已被证明难以击败。

AFAIK,正切函数有实际的dedicated hardware 来计算它的值。此外,Matlab 会自动将大型矩阵的三角函数分布到多个内核上,因此基本上没有什么需要改进的地方。

另外,如果我错了,请纠正我,但考虑到可能的数据开销和内存问题,我认为这种计算在 GPU 上实际上会更慢

现在如果你比较这些:

tic;
for ii = 1:10
    my_sum = C * tan(k * z);
end
toc 

tic;
for ii = 1:10
    my_sum_notan = C * k * z;
end
toc

你会看到所有的痛苦都来自于正切函数,所以你最好专注于这一点。正如您所读到的here,基本上只有在牺牲一些准确性的情况下才能加快触发函数的速度。

你应该问自己这些问题:

  1. 您是否愿意放弃全双精度,或者说,6 位“足够接近”?

  2. 您不能重新制定问题以便之后计算切线吗?还是以前?或者无论如何,元素数量要少得多?在上述问题中,这显然是不可能的,但我不知道完整的代码——可能有一些很好的三角标识可以适用于您的问题。

  3. 鉴于以上所有因素,进一步优化所需的工作量真的超过了更长的运行时间吗?与编写实现残缺但快速的触发函数的自定义、难以移植的 MEX 函数相比,250 秒听起来还不错。

【讨论】:

  • 在这里展示罪魁祸首的好方法(棕褐色)!你得到我的一票:) 你是否肯定 MATLAB 会自动进行多线程触发操作?即使没有安装并行工具包?
  • @DominiqueJacquel 这是 R2007a 中的一项新功能(例如,请参阅 here)。如果您在无限循环中进行计算,您可以在任务管理器/顶部看到这一点;您会看到 MATLAB 正在使用大多数/所有内核。
  • @Rody Oldenhuis 感谢您的完整回答。有时我会考虑在纯 Fortran/C 或使用 GotoBLAS/OpenBLAS 中计算任务,它们更快(在执行时间上)但此类代码的开发速度较慢。
  • @Rody,我撤回了我的声明...重新进行了测试咖啡之后,如果您将三角函数应用于矩阵,它确实使用了多核。好消息:) ...删除了愚蠢的评论!干杯。
【解决方案2】:

尽可能多地预先进行矩阵运算(在这种情况下为转置)以节省循环时间

K = 200;
n_z = 40000;
C = ones(K,1)';
k = (0:K-1)';
z = linspace(0, 2*pi, n_z);

tic;
    my_sum_for = zeros(1, K);
    for i=1:n_z
       my_sum_for(i) = C * tan(k * z(i));
    end
toc

tic;
     my_sum = C * tan(k * z);
toc;

我之前的执行次数

Elapsed time is 1.266158 seconds.
Elapsed time is 0.531173 seconds.

之后

Elapsed time is 0.496803 seconds.
Elapsed time is 0.185396 seconds.

【讨论】:

  • 非常感谢。它真的更快。
【解决方案3】:

这是一个在我的电脑上运行速度稍快的版本:

k = repmat((0:K-1)', 1, n_z);
z = repmat(linspace(0, 2*pi, n_z), K, 1);
C = ones(1, K);
tic
my_sum = C*tan(k.*z);
toc

本质上,我直接对矩阵进行运算,而不是 k 和 z 的外积。

第一版

Elapsed time is 0.652923 seconds.
Elapsed time is 0.240300 seconds.

在 Dominique Jacquel 的回答之后

Elapsed time is 0.376218 seconds.
Elapsed time is 0.214047 seconds.

我的版本

Elapsed time is 0.168535 seconds.

您可能需要添加 repmats 的成本,但也许您只能这样做一次,我不知道其余的代码。

我完全同意 Rody Oldenhuis 的观点。大部分工作在于切线函数。我可以告诉你更多。 k.*z 计算非常有效,不能有太大改进。如果你计算内存带宽,它在我的电脑上大约为 10GB/s。我能得到的峰值大约是 16GB/s,所以很接近。那里没有多少可能性。与 C*T 相同。那是一个简单的 BLAS2 矩阵向量乘法,它是有内存限制的。对于您所显示的系统大小,MATLAB 开销并不太大。

编辑:正如 Rody 提到的,新版本的 MATLAB 已经对 tan() 进行了并行化。所以这里也不多。

您只能希望改进 tan() - 可能通过并行运行它。毕竟,这是一项非常容易并行化的任务……考虑将其导出到 MEX 文件,该文件将使用 OpenMP。非常简单的工作,如果您有几个备用内核,则可以大大加快速度。

【讨论】:

  • 谢谢。有趣的是新版本的MATLAB(2008b测试)不支持mcc -x标志:Error: -x is no longer supported. The MATLAB Compiler no longer generates MEX files because there is no longer any performance advantage to doing so: the MATLAB JIT accelerates M-files by default. To hide proprietary algorithms, use the PCODE function. 可能我会尝试调用外部自制tan-function。
  • 我的意思是编写你自己的 MEX 函数并使用 mex 编译它。这样你就可以跳过整个 repmat / k * z 业务。这只会消耗内存带宽并影响执行时间。 k*z 是一个 BLAS2 操作。您不需要显式创建矩阵。您可以即时计算其条目..
猜你喜欢
  • 2019-03-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多