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 是解决方案,但我在 Mac OS X 版本:10.8.4 上使用的可能是最新版本的 MATLAB 版本:8.1.0.604 (R2013a)。我确实编译了带有和不带有 –largeArrayDims 标志的 mex 文件,但这也没有任何区别。有人建议函数句柄的内容可以直接编码在 cpp 文件中,但这是不可能的,因为我想将此代码提供给任何具有向量输入和实数输出的任何类型函数的用户。

据我所知,mex 文件需要通过 feval 函数才能使用函数句柄,而 m-files 可以直接调用函数句柄,前提是 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
4

3 回答 3

18

所以我尝试自己实现这个,我想我找到了缓慢的原因。

基本上你的代码有一个小的内存泄漏,你没有释放lhs mxArray从调用返回的mexCallMATLAB. 这并不完全是内存泄漏,看到 MATLAB 内存管理器会在 MEX 文件退出时释放内存:

MATLAB 分配动态内存来存储mxArraysin plhs。当您清除 MEX 文件时,MATLAB 会自动释放动态内存。但是,如果堆空间非常宝贵,请mxDestroyArray在完成mxArrays plhs指向时调用。

仍然显式优于隐式......所以你的代码真的强调了 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 函数的开销。

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

于 2013-09-06T17:06:00.503 回答
4

一般来说,绝对没有理由期望 MEX 文件比 M 文件快。这通常是正确的唯一原因是 MATLAB 中的许多循环会产生大量的函数调用开销,以及参数检查等。用 C 重写它可以消除开销,并让您的 C 编译器有机会优化代码。

在这种情况下,C 编译器无需优化……它必须为每次迭代调用 MATLAB 接口。事实上,MATLAB 优化器会做得更好,因为它在某些情况下可以“看到”函数。

换句话说,忘记使用 MEX 来加速这个程序。

于 2013-09-06T15:15:08.160 回答
1

There is some overhead cost in calls from mex to Matlab and vice versa. The overhead per call is small, but it it really adds up in a tight loop like this. As your testing indicates, pure Matlab can be much faster in this case! Your other option is to eliminate the mexCallMATLAB call and do everything in pure C++.

于 2013-09-06T15:10:49.173 回答