8

我有一些需要加速的 Matlab 代码。通过分析,我确定了一个特定的函数是减慢执行速度的罪魁祸首。这个函数在一个循环中被调用了几十万次。

我的第一个想法是将函数转换为 mex(使用 Matlab Coder)以加快速度。但是,常识告诉我 Matlab 和 mex 代码之间的接口会导致一些开销,这意味着调用这个 mex 函数数千次可能不是一个好主意。它是否正确?或者当重复调用同一个 mex 以消除开销时,Matlab 是否会发挥一些作用?

如果有很大的开销,我正在考虑重组代码,以便将循环添加到函数本身,然后创建一个 mex。在此之前,我想验证我的假设,以证明在此花费的时间是合理的。

更新:

我尝试了@angainor 的建议,并使用以下代码创建了 donthing.m:

function nothing = donothing(dummy) %#codegen
nothing = dummy;
end

然后,我从中创建了一个 mex 函数作为 donothing_mex,并尝试了以下代码:

tic;
for i=1:1000000
    donothing_mex(5);
end
toc;

结果是对该函数的一百万次调用花费了大约 9 秒。对于我们的目的来说,这不是一个显着的开销,所以现在我想我会将被调用的函数单独转换为 mex。然而,回想起来,从一个执行大约一百万次的循环中调用一个函数确实是一个非常愚蠢的想法,考虑到这是性能关键代码,因此将循环移动到 mex 函数仍然在书中,但优先级要低得多。

4

3 回答 3

5

像往常一样,这完全取决于您在 MEX 文件中所做的工作量。调用 MEX 函数的开销是恒定的,不取决于问题的大小等。这意味着参数不会复制到新的临时数组中。因此,如果工作量足够,调用 MEX 文件的 MATLAB 开销将不会显示。无论如何,根据我的经验,MEX 调用开销仅在第一次调用 mex 函数时显着 - 必须加载动态库、解析符号等。后续 MEX 调用的开销非常小并且非常高效。

由于这种高级语言的性质,MATLAB 中的几乎所有内容都与一些开销有关。除非你有一个代码,你确定它是用 JIT 完全编译的(但是你不需要 mex 文件:))所以你可以选择一个开销而不是另一个开销..

总结一下 - 我不会太害怕 MEX 调用开销。

编辑正如在这里和其他地方经常听到的那样,在任何特定情况下唯一合理的做法当然是BENCHMARK并自己检查它。您可以通过编写一个简单的 MEX 函数轻松估计 MEX 调用开销:

#include "mex.h"
void mexFunction(int nlhs, mxArray *plhs[ ], int nrhs, const mxArray *prhs[ ]) 
{      
}

在我的电脑上你得到

tic; for i=1:1000000; mexFun; end; toc
Elapsed time is 2.104849 seconds.

这是每个 MEX 调用 2e-6s 的开销。添加您的代码,计时并查看开销是否处于可接受的水平。

正如 Andrew Janke 在下面指出的(谢谢!),MEX 函数开销显然取决于您传递给 MEX 函数的参数数量。这是一个小的依赖,但它存在:

a = ones(1000,1);
tic; for i=1:1000000; mexFun(a); end; toc
Elapsed time is 2.41 seconds.

它与 的大小无关a

a = ones(1000000,1);
tic; for i=1:1000000; mexFun(a); end; toc
Elapsed time is 2.41805 seconds.

但这与参数的数量有关

a = ones(1000000,1);
b = ones(1000000,1);
tic; for i=1:1000000; mexFun(a, b); end; toc
Elapsed time is 2.690237 seconds.

因此,您可能希望在测试中考虑到这一点。

于 2012-10-16T19:09:58.477 回答
2

您绝对应该毫不犹豫地将循环移动到 mex 文件中。下面的示例演示了 for 循环中几乎空的工作单元的 1000 倍加速。显然,随着 for 循环中工作量的变化,这种加速会降低。

这是差异的示例:

没有内部循环的 Mex 函数:

#include "mex.h"
void mexFunction(int nlhs, mxArray *plhs[ ], int nrhs, const mxArray *prhs[ ]) 
{      
    int i=1;    
    plhs[0] = mxCreateDoubleScalar(i);
}

在 Matlab 中调用:

tic;for i=1:1000000;donothing();end;toc
Elapsed time is 3.683634 seconds.

带有内部循环的 Mex 函数:

#include "mex.h"
void mexFunction(int nlhs, mxArray *plhs[ ], int nrhs, const mxArray *prhs[ ]) 
{      
    int M = mxGetScalar(prhs[0]);
    plhs[0] = mxCreateNumericMatrix(M, 1, mxDOUBLE_CLASS, mxREAL);
    double* mymat = mxGetPr(plhs[0]);
    for (int i=0; i< M; i++)
        mymat[i] = M-i;
}

在 Matlab 中调用:

tic; a = donothing(1000000); toc
Elapsed time is 0.003350 seconds.
于 2012-10-17T07:53:02.467 回答
2

好吧,这是我在 Matlab 中能做到的最快速度:

%#eml
function L = test(s,t)

    m = numel(s);
    n = numel(t);

    % trivial cases
    if m==0 && n==0
        L = 0; return; end
    if n==0
        L = m; return; end
    if m==0
        L = n; return; end

    % non-trivial cases
    M = zeros(m+1,n+1);    
    M(:,1) = 0:m;

    for j = 2:n+1
        for i = 2:m+1
            M(i,j) = min([
                M(i-1,j) + 1
                M(i,j-1) + 1
                M(i-1,j-1) + (s(i-1)~=t(j-1));
                ]);
        end
    end

    L = min(M(end,:));

end

你能编译这个并运行一些测试吗?(出于某种奇怪的原因,编译无法在我的安装上运行......)如果您认为这更容易,也许可以更改%#eml为first。%#codegen

注意:对于 C 版本,您还应该交换 for 循环,以便循环j是内部循环。

此外,row1androw2方法的内存效率要高得多。如果你无论如何都要编译,我会使用这种方法。

于 2012-10-18T09:01:26.617 回答