Matlab mex文件使用mexCallMATLAB比对应的m文件慢了近300倍。

9
我开始使用C++实现一些m文件,以减少运行时间。这些m文件生成n维点并在这些点上评估函数值。这些函数是用户定义的,并作为函数句柄传递给m文件和mex文件。mex文件使用mexCallMATLAB和feval来查找函数值。
我构建了下面的示例,在Matlab命令行中构建的函数句柄fn被传递到matlabcallingmatlab.m和mexcallingmatlab.cpp程序中。在一个新打开的Matlab中,mexcallingmatlab在241.5秒内评估了这个函数200000次,而matlabcallingmatlab在0.81522秒内评估了它,因此mex实现的速度慢了296倍。这些时间是第二次运行的结果,因为第一次运行似乎更大,可能是由于一些开销与首次加载程序等相关。
我花了很多天在网上搜索这个问题,并尝试了一些建议。我尝试了不同的mex编译标志来优化mex,但性能几乎没有差别。Stackoverflow上的一篇早期帖子指出升级Matlab是解决方案,但我使用的可能是最新版本MATLAB Version: 8.1.0.604 (R2013a) on Mac OS X Version: 10.8.4。我确实用和没有用-largeArrayDims标志编译了mex文件,但这也没有任何区别。有些人建议可以直接在cpp文件中编写函数句柄的内容,但这是不可能的,因为我想为任何具有向量输入和实数输出的用户提供此代码。
据我所知,mex文件需要通过feval函数来使用函数句柄,而m文件可以直接调用函数句柄,前提是Matlab版本比某个版本更新。
非常感谢任何帮助。
在Matlab命令行中创建的简单函数句柄:
fn = @(x) x'*x 

matlabcallingmatlab.m :

function matlabcallingmatlab( fn )
x = zeros(2,1); 
for i = 0 : 199999
    x(2) = i; 
    f = fn( x ); 
end

mexcallingmatlab.cpp:

#include "mex.h"
#include <cstring>

void mexFunction( int nlhs, mxArray *plhs[],
                  int nrhs, const mxArray *prhs[] )
{
    mxArray *lhs[1], *rhs[2]; //parameters to be passed to feval
    double f, *xptr, x[] = {0.0, 0.0}; // x: input to f and f=f(x)
    int n = 2, nbytes = n * sizeof(double);  // n: dimension of input x to f

    // prhs[0] is the function handle as first argument to feval
    rhs[0] = const_cast<mxArray *>( prhs[0] );

    // rhs[1] contains input x to the function
    rhs[1] = mxCreateDoubleMatrix( n, 1, mxREAL);
    xptr = mxGetPr( rhs[1] );

    for (int i = 0; i < 200000; ++i)
    {
        x[1] = double(i);   // change input 
        memcpy( xptr, x, nbytes );  // now rhs[1] has new x
        mexCallMATLAB(1, lhs, 2, rhs, "feval");
        f = *mxGetPr( lhs[0] );
    }
}

mex文件的编译

>> mex -v -largeArrayDims mexcallingmatlab.cpp

1
所以,您正在使用C++调用一个执行“x * x”的Matlab函数?如果Matlab比您的C++解决方案更好,我不会感到惊讶。因为Matlab代码不必跳过整个范围的障碍来将数据从C++格式转换为Matlab格式,然后再转换回C++格式。 - Mats Petersson
1
你确定你没有只是测量了调用 mexCallMATLAB 200000 次的开销吗? - High Performance Mark
@Mats:我为了简单起见放了这个示例函数。我尝试了许多其他更复杂的函数,结果都相似。 - Meteor
@Meteor:我的评论是Peter答案的简略版。 - Mats Petersson
1
@Meteor:虽然其他人说的是对的,但真正的问题在于你依赖于MEX文件中自动释放内存的功能,这对于数千个mxArray来说可能非常缓慢。因此,如果你在调用后明确清理内存,问题就会消失。请参见我下面的答案... - Amro
显示剩余2条评论
3个回答

18

所以我试着自己实现这个功能,我认为我找到了慢的原因。

基本上你的代码存在一个小内存泄漏,在调用mexCallMATLAB后,你没有释放返回的lhsmxArray。它不完全是一个内存泄漏,因为MATLAB内存管理器会在MEX文件退出时释放内存:

MATLAB分配动态内存来存储plhs中的mxArrays。 当您清除MEX文件时,MATLAB会自动取消分配动态内存。 但是,如果堆空间很紧张,请在使用plhs指向的mxArrays之后调用mxDestroyArray

尽管如此,显式总比隐式好... 所以你的代码确实在压力测试MATLAB内存管理器的解分配器 :)

mexcallingmatlab.cpp

#include "mex.h"

#ifndef N
#define N 100
#endif

void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
    // validate input/output arguments
    if (nrhs != 1) {
        mexErrMsgTxt("One input argument required.");
    }
    if (mxGetClassID(prhs[0]) != mxFUNCTION_CLASS) {
        mexErrMsgTxt("Input must be a function handle.");
    }
    if (nlhs > 1) {
        mexErrMsgTxt("Too many output arguments.");
    }

    // allocate output
    plhs[0] = mxCreateDoubleMatrix(N, 1, mxREAL);
    double *out = mxGetPr(plhs[0]);

    // prepare for mexCallMATLAB: val = feval(@fh, zeros(2,1))
    mxArray *lhs, *rhs[2];
    rhs[0] = mxDuplicateArray(prhs[0]);
    rhs[1] = mxCreateDoubleMatrix(2, 1, mxREAL);
    double *xptr = mxGetPr(rhs[1]) + 1;

    for (int i=0; i<N; ++i) {
        *xptr = i;
        mexCallMATLAB(1, &lhs, 2, rhs, "feval");
        out[i] = *mxGetPr(lhs);
        mxDestroyArray(lhs);
    }

    // cleanup
    mxDestroyArray(rhs[0]);
    mxDestroyArray(rhs[1]);
}

MATLAB

fh = @(x) x'*x;
N = 2e5;

% MATLAB
tic
out = zeros(N,1);
for i=0:N-1
    out(i+1) = feval(fh, [0;i]);
end
toc

% MEX
mex('-largeArrayDims', sprintf('-DN=%d',N), 'mexcallingmatlab.cpp')
tic
out2 = mexcallingmatlab(fh);
toc

% check results
assert(isequal(out,out2))

运行上述基准测试数次(以预热),我得到了以下一致的结果:

Elapsed time is 0.732890 seconds.    % pure MATLAB
Elapsed time is 1.621439 seconds.    % MEX-file

与您最初拥有的慢速时间相去甚远!但是纯MATLAB部分大约快了两倍,可能是由于调用外部MEX函数的开销。

(我的系统:运行64位R2013a的Win8)


1
非常感谢。在我的代码中,在for循环中添加mxDestroyArray(lhs [0]);行解决了问题。现在我几乎拥有相同的运行时间,都约为0.8秒。这种性能肯定是可以接受的。我也要感谢其他人提供的信息性评论。 - Meteor

4
没有理由认为 MEX 文件总体上比 M 文件更快。通常情况下,唯一的原因是 MATLAB 中的许多循环都会产生大量的函数调用开销和参数检查等操作。将其改写为 C 可以消除这些开销,并给您的 C 编译器优化代码的机会。
但在这种情况下,C 编译器没有什么可以优化的...... 它必须为每次迭代进行 MATLAB 接口调用。事实上,MATLAB 优化器将做得更好,因为它有时可以“看到”函数内部。
换句话说,请忘记使用 MEX 来加速此程序。

这段代码仅代表整个程序的一小部分。C编译器在其他所有部分表现得非常出色,但这一部分是瓶颈。 - Meteor

1

从mex到Matlab以及反向调用都存在一定的开销成本。每次调用的开销很小,但在像这样的紧密循环中,它确实会累加起来。正如您的测试所示,在这种情况下,纯Matlab可能会更快!您的另一个选择是消除mexCallMATLAB调用并在纯C++中完成所有操作。


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