为什么在到达 MEX 文件的最后一行后需要这么长时间 return 到 Matlab?

Why does it take such a long time to return to Matlab after reaching the last line of a MEX file?

在我的 MEX 文件的最后一行完成执行后,return 到 matlab 命令行大约需要 14 秒。

当来自 matlab 的 MEX 文件时序:

D=rand(14000)+rand(14000)*1i;
tic;
[A B C]=myMexFile(D);
toc
disp(datetime('now'));

输出为:

Elapsed time is 35.192704 seconds.
   15-Sep-2018 16:51:35

使用以下最小工作示例从 C 中为 MEX 文件计时:

#include <mex.h>
#include <sys/time.h>
#include <time.h>
#include <cuComplex.h>

double getHighResolutionTime() {
    struct timeval tod;
    gettimeofday(&tod, NULL);
    double time_seconds = (double) tod.tv_sec + ((double) tod.tv_usec / 1000000.0);
    return time_seconds;
}

void double2cuDoubleComplex(cuDoubleComplex* p, double* pr, double* pi,int numElements){
    for(int j=0;j<numElements;j++){
        p[j].x=pr[j];
        p[j].y=pi[j];
    }
}

void cuDoubleComplex2double(cuDoubleComplex* p, double* pr, double* pi,int numElements){
    for(int j=0;j<numElements;j++){
        pr[j]= p[j].x;
        pi[j]= p[j].y;
    }
}

void mexFunction( int nlhs, mxArray *plhs[],int nrhs, const mxArray *prhs[]) {

    double tic=getHighResolutionTime();

    int m=(int)mxGetM(prhs[0]);
    int n=(int)mxGetN(prhs[0]);
    int SIZE=m*n;

    //get pointers to input data from matlab and convert to 
    //interleaved (Fortran) ordering
    cuDoubleComplex *Gr= (cuDoubleComplex*) mxMalloc(SIZE*sizeof(cuDoubleComplex));
    double2cuDoubleComplex(Gr,mxGetPr(prhs[0]),mxGetPi(prhs[0]),SIZE);


    //modify the input data, allocate output matrices, and convert 
    //back to split (matlab) ordering.
    Gr[0].x=0.0;
    plhs[0] = mxCreateDoubleMatrix(m,m,mxCOMPLEX);
    cuDoubleComplex2double(Gr,mxGetPr(plhs[0]),mxGetPi(plhs[0]),SIZE);

    Gr[0].x=1.0;
    plhs[1] = mxCreateDoubleMatrix(m,m,mxCOMPLEX);
    cuDoubleComplex2double(Gr,mxGetPr(plhs[1]),mxGetPi(plhs[1]),SIZE);

    Gr[0].x=2.0;
    plhs[2] = mxCreateDoubleMatrix(m,m,mxCOMPLEX);
    cuDoubleComplex2double(Gr,mxGetPr(plhs[2]),mxGetPi(plhs[2]),SIZE);

    mxFree(Gr);

    double elapsed=getHighResolutionTime()-tic;mexPrintf("%f\n", elapsed);
    time_t current_time = time(NULL);
    char* c_time_string = ctime(&current_time);
    mexPrintf("time at end of MEX file %s\n", c_time_string);
}

输出为:

21.676793
time at end of MEX file Sat Sep 15 16:51:21 2018

Matlab returns 的时间为 35.19s,而 MEX 文件实际上需要 21.67s 才能到达最后一行。日期和时间相隔约 14 秒,即 MEX 文件的 16:51:21 和 matlab 的 16:51:35。

输出是非常大的矩阵,但在 MEX 文件的最后一行之前已成功分配和初始化。我想不出别的了。是什么导致了这种行为,我该如何避免?

更新:我在更多的机器上试过了,时间差异仍然存在。

更新:我用一个最小的工作示例替换了上面的 pseudo-code。请注意,上述代码实际上并未使用任何 GPU 功能。我包括 cuComplex.h header 只是为了使用 cuDoubleComplex 数据类型。

自 MATLAB R2018a 起,MATLAB internally stores complex arrays in an interleaved format。在以前的版本中,MATLAB 使用两个独立的内存块来存储复数数据:一个用于实数值,一个用于虚数值。在 MEX 文件中,您使用 mxGetPr()mxGetPi() 获取指向这两个内存块的指针(这些函数称为 "Separate Complex API")。

从 R2018a 开始,有了新的内部数据表示,MEX 文件可以用两种不同的方式编译:

  1. 一种兼容模式(这是默认模式,您可以将 -R2017b 添加到 mex 命令以强制使用此模式),您可以在其中编译旧的 MEX 文件而无需修改。因此,这些 MEX 文件使用 "Separate Complex API"。在执行 MEX 文件代码之前,MATLAB 将复数数据从其新的交错表示复制到单独的实部和虚部内存块中,并将任何复数输出数组复制回交错格式。这显然需要一些时间。这是 OP 观察到的延迟的原因。

  2. 一种新模式(将 -R2018a 添加到 mex 命令),其中 MEX 文件使用 the new "Interleaved Complex API"。也就是说,MEX 文件代码适用于使用新的交错复数格式。由于您可能希望从 MEX 文件调用的大多数 C 和 C++ 库都使用交错格式,这实际上是一个很大的优势。

避免处理复杂数组的 MEX 文件在开始和结束处出现较大延迟的解决方案是重写它们以使用新的 "Interleaved Complex API"。这需要 the following changes:

  • 查找 mxGetPr()mxGetPi() 函数的所有用法。后者不再可用。 mxGetPr() 现在如果输入数组是复数值会抛出错误。相反,使用 mxGetData(), which will return a pointer to the complex interleaved data. Note that they recommend you don't use it for numeric data, it seems they prefer you use the new "typed data access functions"mxGetImagData(),与mxGetPi()一样,已不存在。

  • 设置数据指针(mxSet...())的函数也是如此

  • 不要忘记使用 mxIsComplex() and mxIsDouble().

  • 检查输入数组是否真的是复数和双精度类型
  • 函数 mxGetElementSize 现在 returns 16 用于复杂的双精度数据,而不是以前的 8。

  • 使用 mex -R2018a <filename>.

  • 编译您的 MEX 文件

Here are some more troubleshooting tips.