如何加快这个mex代码?

时间:2012-09-04 22:28:33

标签: c performance matlab optimization mex

我在mex中重新编程一段MATLAB代码(使用C)。到目前为止,我的MATLAB代码的C版本大约是MATLAB代码的两倍。现在我有三个问题,都与下面的代码有关:

  1. 如何更多地加快此代码的速度?
  2. 您是否发现此代码存在任何问题?我问这个因为我不太了解mex而且我也不是C大师;-) ...我知道代码中应该有一些检查(例如,如果仍有堆空间的话使用realloc,但为了简单起见,我把它留下了)
  3. MATLAB是否有可能进行优化,我真的无法在C中获得超过两倍的代码......?
  4. 代码应该或多或少与平台无关(Win,Linux,Unix,Mac,不同的硬件),所以我不想使用汇编程序或特定的线性代数库。这就是我自己为员工编程的原因......

    #include <mex.h>
    #include <math.h>
    #include <matrix.h>
    
    void mexFunction(
        int nlhs, mxArray *plhs[],
        int nrhs, const mxArray *prhs[])
    {
        double epsilon = ((double)(mxGetScalar(prhs[0])));
        int strengthDim = ((int)(mxGetScalar(prhs[1])));
        int lenPartMat = ((int)(mxGetScalar(prhs[2])));
        int numParts = ((int)(mxGetScalar(prhs[3])));
        double *partMat = mxGetPr(prhs[4]);
        const mxArray* verletListCells = prhs[5];
        mxArray *verletList;
    
        double *pseSum = (double *) malloc(numParts * sizeof(double));
        for(int i = 0; i < numParts; i++) pseSum[i] = 0.0;
    
        float *tempVar = NULL;
    
        for(int i = 0; i < numParts; i++)
        {
            verletList = mxGetCell(verletListCells,i);
            int numberVerlet = mxGetM(verletList);
    
            tempVar = (float *) realloc(tempVar, numberVerlet * sizeof(float) * 2);
    
    
            for(int a = 0; a < numberVerlet; a++)
            {
                tempVar[a*2] = partMat[((int) (*(mxGetPr(verletList) + a))) - 1] - partMat[i];
                tempVar[a*2 + 1] = partMat[((int) (*(mxGetPr(verletList) + a))) - 1 + lenPartMat] - partMat[i + lenPartMat];
    
                tempVar[a*2] = pow(tempVar[a*2],2);
                tempVar[a*2 + 1] = pow(tempVar[a*2 + 1],2);
    
                tempVar[a*2] = tempVar[a*2] + tempVar[a*2 + 1];
                tempVar[a*2] = sqrt(tempVar[a*2]);
    
                tempVar[a*2] = 4.0/(pow(epsilon,2) * M_PI) * exp(-(pow((tempVar[a*2]/epsilon),2)));
                pseSum[i] = pseSum[i] + ((partMat[((int) (*(mxGetPr(verletList) + a))) - 1 + 2*lenPartMat] - partMat[i + (2 * lenPartMat)]) * tempVar[a*2]);
            }
    
        }
    
        plhs[0] = mxCreateDoubleMatrix(numParts,1,mxREAL);
        for(int a = 0; a < numParts; a++)
        {
            *(mxGetPr(plhs[0]) + a) = pseSum[a];
        }
    
        free(tempVar);
        free(pseSum);
    }
    

    所以这是改进版本,比MATLAB版本快约12倍。转换的事情仍然花费很多时间,但我暂时放弃了,因为我必须在MATLAB中为此改变一些东西。所以首先关注剩下的C代码。您是否在以下代码中看到了更多潜力?

    #include <mex.h>
    #include <math.h>
    #include <matrix.h>
    
    void mexFunction(
        int nlhs, mxArray *plhs[],
        int nrhs, const mxArray *prhs[])
    {
        double epsilon = ((double)(mxGetScalar(prhs[0])));
        int strengthDim = ((int)(mxGetScalar(prhs[1])));
        int lenPartMat = ((int)(mxGetScalar(prhs[2])));
        double *partMat = mxGetPr(prhs[3]);
        const mxArray* verletListCells = prhs[4];
        int numParts = mxGetM(verletListCells);
        mxArray *verletList;
    
        plhs[0] = mxCreateDoubleMatrix(numParts,1,mxREAL);
        double *pseSum = mxGetPr(plhs[0]);
    
        double epsilonSquared = epsilon*epsilon;
    
        double preConst = 4.0/((epsilonSquared) * M_PI);
    
        int numberVerlet = 0;
    
        double tempVar[2];
    
        for(int i = 0; i < numParts; i++)
        {
            verletList = mxGetCell(verletListCells,i);
            double *verletListPtr = mxGetPr(verletList);
            numberVerlet = mxGetM(verletList);
    
            for(int a = 0; a < numberVerlet; a++)
            {
                int adress = ((int) (*(verletListPtr + a))) - 1;
    
                tempVar[0] = partMat[adress] - partMat[i];
                tempVar[1] = partMat[adress + lenPartMat] - partMat[i + lenPartMat];
    
                tempVar[0] = tempVar[0]*tempVar[0] + tempVar[1]*tempVar[1];
    
                tempVar[0] = preConst * exp(-(tempVar[0]/epsilonSquared));
                pseSum[i] += ((partMat[adress + 2*lenPartMat] - partMat[i + (2*lenPartMat)]* tempVar[0]);
            }
    
        }
    
    }
    

2 个答案:

答案 0 :(得分:2)

你能提前估计tempVar的最大大小是什么,并在循环之前为它分配内存而不是使用realloc?重新分配内存是一项耗时的操作,如果numParts很大,这可能会产生巨大影响。看看this question

答案 1 :(得分:2)

  • 您无需为本地使用分配pseSum,然后将数据复制到输出。您可以简单地分配一个MATLAB对象并获取指向内存的指针:

    plhs[0] = mxCreateDoubleMatrix(numParts,1,mxREAL);
    pseSum  = mxGetPr(plhs[0]);
    

因此,您不必将pseSum初始化为0,因为MATLAB已在mxCreateDoubleMatrix中执行此操作。

  • 从内循环中删除所有mxGetPr并将其分配给变量。

  • 考虑在MATLAB中使用int32或uint32数组,而不是将双精度转换为整数。将double转换为int是昂贵的。内部循环计算看起来像

    tempVar[a*2] = partMat[somevar[a] - 1] - partMat[i];
    

    您在代码中使用此类构造

    ((int) (*(mxGetPr(verletList) + a)))
    

    你这样做是因为varletList是一个'double'数组(默认情况下在MATLAB中就是这种情况),它保存整数值。相反,您应该使用整数数组。在MATLAB中调用mex文件类型之前:

    varletList = int32(varletList);
    

    然后你不需要上面的int类型。你只需写

    ((int*)mxGetData(verletList))[a]
    

    或者更好,早点分配

    somevar = (int*)mxGetData(verletList);
    

    后来写

    somevar[a]
    
  • 在所有循环之前预先计算4.0 /(pow(epsilon,2)* M_PI)!这是一个昂贵的常数。

  • pow((tempVar [a * 2] / epsilon),2))只是tempVar [a * 2] ^ 2 / epsilon ^ 2。你刚才计算sqrt(tempVar [a * 2])。你为什么现在把它放在一边?

  • 一般不要使用pow(x,2)。只需写x * x

  • 我会在参数上添加一些健全性检查,特别是如果你需要整数。要么使用MATLABs int32 / uint32类型,要么检查你实际获得的是一个整数。

新代码中的

修改

  • 在循环之前计算-1 / epsilonSquared并计算exp(minvepssq * tempVar [0])。注意结果可能略有不同。取决于您的需求,但如果您不关心确切的操作顺序,请执行此操作。

  • 定义一个寄存器变量preSum_r并用它来对内循环中的结果求和。循环后将其分配给preSum [i]。如果您想要更多乐趣,可以使用SSE流媒体商店(_mm_stream_pd编译器内在函数)将结果写入内存。

  • 删除double to int cast

  • 很可能无关紧要,但尝试将tempVar [0/1]更改为正常变量。不相关,因为编译器应该为您做到这一点。但同样,这里不需要数组。

  • 使用OpenMP并行化外部循环。琐碎(至少是最简单的版本而不考虑NUMA架构的数据布局),因为迭代之间没有依赖性。