Performance Matlab:从循环中重复调用同一个mex函数会产生太多开销吗?

Performance Matlab:从循环中重复调用同一个mex函数会产生太多开销吗?,performance,matlab,mex,Performance,Matlab,Mex,我有一些需要加速的Matlab代码。通过分析,我发现一个特定的函数是导致执行速度减慢的罪魁祸首。这个函数在一个循环中被调用数十万次 我的第一个想法是将函数转换为mex(使用Matlab编码器)以加快速度。然而,一般编程常识告诉我,Matlab和mex代码之间的接口将导致一些开销,这意味着调用此mex函数数千次可能不是一个好主意。这是正确的吗?或者,当重复调用同一个mex以消除开销时,Matlab是否发挥了一些魔力 如果有很大的开销,我会考虑重新构造代码,以便将循环添加到函数本身,然后创建一个me

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

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

如果有很大的开销,我会考虑重新构造代码,以便将循环添加到函数本身,然后创建一个mex。在这样做之前,我想验证我的假设,以证明花在这方面的时间是合理的

更新:

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

function nothing = donothing(dummy) %#codegen
nothing = dummy;
end
tic;
for i=1:1000000
    donothing_mex(5);
end
toc;
然后,我在此基础上创建了一个名为donothing_mex的mex函数,并尝试了以下代码:

function nothing = donothing(dummy) %#codegen
nothing = dummy;
end
tic;
for i=1:1000000
    donothing_mex(5);
end
toc;

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

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

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

所以总结一下——我不会太害怕MEX在头顶上打电话

编辑正如在这里和其他地方经常听到的那样,在任何特定情况下,唯一合理的做法当然是对进行基准测试并自行检查。通过编写一个简单的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.

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

您应该毫不犹豫地在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);
}
#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;for i=1:1000000;donothing();end;toc
Elapsed time is 3.683634 seconds.
tic; a = donothing(1000000); toc
Elapsed time is 0.003350 seconds.
带内部回路的Mex功能:

#include "mex.h"
void mexFunction(int nlhs, mxArray *plhs[ ], int nrhs, const mxArray *prhs[ ]) 
{      
    int i=1;    
    plhs[0] = mxCreateDoubleScalar(i);
}
#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中能做到的最快速度:

%#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
更改为
%\codegen

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


另外,
row1
row2
方法的内存效率要高得多。如果要编译,我会使用这种方法

里面的代码实际上很小-基本上,它只是一个Levenshtein距离计算器函数。@sundar我不能确切地告诉你你能期望什么,因为我不知道代码和你处理的问题。如果您不想投入太多时间,为什么不编写一个简单的mex文件,它只需添加两个数字,或者返回
x+1
,并测量开销?正如我所说,它是常数,但显然取决于您的硬件/os/matlab版本。您可以将其添加到C代码的性能中,并查看在MATLAB+MEX中的预期效果。我已经用开销测量更新了这个问题,并决定暂时推迟移动循环。我会等到明天,以防万一有其他答案出现,然后我会接受你的答案。@sundar我刚刚自己做了测试:)1e6呼叫2秒。。嗯,这绝对不是什么。如果您使用JIT,那么调用一个普通的matlab函数(!)的开销会更大。因此,由于mex文件的原因,您确实需要获得一些加速…还要注意,开销相对于数组大小是恒定的,但可能会随着函数参数的数量而增加;原始数据在写入时与copy共享,但是mxarray数据结构的新“头”部分被构建并传递给函数,至少在我所看到的版本中是这样。(这可能是Matlab的防御,可能解释了一些更高的MEX文件开销。)明确地将循环移到代码中。它应该只需要2到3行额外的mex代码,这9行代码可能会为您节省8.5秒