1

mexでMATLABコードの一部を再プログラミングしています(Cを使用)。これまでのところ、MATLABコードのCバージョンはMATLABコードの約2倍の速度です。ここで、3つの質問があります。これらはすべて、以下のコードに関連しています。

  1. このコードをさらに高速化するにはどうすればよいですか?
  2. このコードに問題がありますか?mexをよく知らず、Cの第一人者でもないので、これを尋ねます;-) ...コードにいくつかのチェックが必要であることを認識しています(たとえば、使用中にヒープスペースがまだある場合realloc)。しかし、私は今のところ簡単にするためにこれを残しました)
  3. MATLABが非常にうまく最適化されているため、Cで2倍以上の高速コードを取得できない可能性はありますか?

コードは多かれ少なかれプラットフォームに依存しないはずなので(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]);
        }

    }

}
4

2 に答える 2

2
  • ローカルで使用するためにpseSumを割り当ててから、後でデータを出力にコピーする必要はありません。単純にMATLABオブジェクトを割り当てて、メモリへのポインタを取得できます。

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

したがって、MATLABはすでにmxCreateDoubleMatrixで初期化を行っているため、pseSumを0に初期化する必要はありません。

  • 内部ループからすべてのmxGetPrを削除し、前にそれらを変数に割り当てます。

  • doubleをintにキャストする代わりに、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)を事前計算します!それは1つの高価な定数です。

  • pow((tempVar [a * 2] / epsilon)、2))は、単にtempVar [a * 2] ^ 2 / epsilon^2です。直前にsqrt(tempVar [a * 2])を計算します。なぜ今それを二乗するのですか?

  • 通常、pow(x、2)は使用しないでください。x*xと書くだけ

  • 特に整数が必要な場合は、パラメーターにいくつかの健全性チェックを追加します。MATLABのint32/uint32型を使用するか、実際に取得するものが整数であることを確認してください。

新しいコードで編集

  • ループの前に-1/epsilonSquaredを計算し、exp(minvepssq * tempVar [0])を計算します。結果はわずかに異なる場合があることに注意してください。必要なものによって異なりますが、操作の正確な順序を気にしない場合は、それを実行してください。

  • レジスタ変数preSum_rを定義し、それを使用して内部ループの結果を合計します。ループの後、それをpreSum[i]に割り当てます。もっと楽しみたい場合は、SSEストリーミングストア(_mm_stream_pdコンパイラ組み込み関数)を使用して結果をメモリに書き込むことができます。

  • ダブルを削除してintキャストしますか

  • ほとんどの場合無関係ですが、tempVar[0/1]を通常の変数に変更してみてください。コンパイラがあなたに代わってそれを行うはずなので、無関係です。ただし、ここでも配列は必要ありません。

  • OpenMPを使用して外部ループを並列化します。反復間に依存関係がないため、些細なことです(少なくとも、NUMAアーキテクチャのデータレイアウトを考慮しない最も単純なバージョン)。

于 2012-09-05T07:28:51.500 に答える
2

tempVarreallocを使用する代わりに、ループの前に最大サイズを見積もり、それにメモリを割り当てることができますか?メモリの再割り当ては時間のかかる操作であり、メモリがnumParts大きい場合、これは大きな影響を与える可能性があります。この質問を見てください。

于 2012-09-05T00:46:27.820 に答える