Как ускорить этот код mex?

Я перепрограммирую часть кода MATLAB в mex (используя C). На данный момент моя C-версия кода MATLAB примерно в два раза быстрее, чем код MATLAB. Теперь у меня есть три вопроса, все связанные с кодом ниже:

  1. Как еще ускорить этот код?
  2. Вы видите какие-либо проблемы с этим кодом? Я спрашиваю об этом, потому что я не очень хорошо знаю mex, а также я не гуру C ;-) ... я знаю, что в коде должны быть некоторые проверки (например, есть ли еще место в куче при использовании realloc , но я оставил это для простоты на данный момент)
  3. Возможно ли, что MATLAB так хорошо оптимизируется, что я действительно не могу получить более чем в два раза более быстрый код на C...?

Код должен быть более или менее независимым от платформы (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);
}

Итак, это улучшенная версия, которая примерно в 12 раз быстрее, чем версия MATLAB. Преобразование по-прежнему отнимает много времени, но я пока оставляю это, потому что для этого мне нужно что-то изменить в 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]);
        }

    }

}

person Michael    schedule 04.09.2012    source источник
comment
Можете ли вы также опубликовать исходный код Matlab. Часто наилучшая оптимизация скорости выполняется на уровне разработки алгоритма.   -  person learnvst    schedule 05.09.2012


Ответы (2)


  • Вам не нужно выделять pseSum для локального использования, а затем копировать данные в выходные данные. Вы можете просто выделить объект MATLAB и получить указатель на память:

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

Таким образом, вам не придется инициализировать pseSum значением 0, потому что MATLAB уже делает это в mxCreateDoubleMatrix.

  • Удалите все mxGetPr из внутреннего цикла и перед этим назначьте их переменным.

  • Вместо приведения двойных чисел к целым числам рассмотрите возможность использования массивов int32 или uint32 в MATLAB. Преобразование double в int обходится дорого. Вычисления внутреннего цикла будут выглядеть так

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

    Вы используете такие конструкции в своем коде

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

    Вы делаете это, потому что varletList является «двойным» массивом (это случай по умолчанию в MATLAB), который содержит целые значения. Вместо этого вы должны использовать целочисленный массив. Прежде чем вы вызовете свой тип файла mex в MATLAB:

    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). Просто напишите х*х

  • Я бы добавил некоторые проверки работоспособности параметров, особенно если вам нужны целые числа. Либо используйте тип 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-архитектур), так как нет зависимости между итерациями.

person angainor    schedule 05.09.2012
comment
Спасибо ребята за комментарии!!! Некоторые из них действительно просты или просто базовая математика. Я должен был увидеть это сам :-( Теперь я получил свой код примерно в 10 раз быстрее, чем код Matlab, что меня вполне устраивает. Я реализовал все ваши подсказки, кроме одного от angainor о приведении двойников. Можете ли вы сказать мне еще немного об этом, не очень понимаю... (особенно как это сделать) И почему x*x лучше, чем pow(x,2)? - person Michael; 05.09.2012
comment
Вы правы в том, что это может и не иметь значения - если компилятор достаточно хорош, он найдет эту оптимизацию и вместо вызова функции pow, которая в общем случае дороже, просто выполнит X*x. Но зачем рисковать? То же самое с дорогой константой, вычисляемой внутри цикла. Компилятор может найти его, но зачем вообще его туда помещать? В конце концов, вы должны просто сравнить и проверить. - person angainor; 05.09.2012
comment
Другой вопрос. Я немного погуглил об этом, но я все еще не уверен. Есть ли более быстрая версия exp() в C??? Конечно, этот максимально оптимизирован, но, возможно, некоторые из них немного теряют точность, но все же достаточно хороши для моей цели... - person Michael; 06.09.2012
comment
Я предлагаю опубликовать новый вопрос о exp. Свяжите этот вопрос там, чтобы показать, какой тип кода у вас есть. - person angainor; 06.09.2012
comment
Эй, спасибо за ваши предложения. Теперь я использую openMP, и это ускоряет процесс!!! Так что теперь это действительно достаточно быстро! - person Michael; 07.09.2012

Можете ли вы заранее оценить максимальный размер tempVar и выделить для него память перед циклом вместо использования realloc? Перераспределение памяти — это трудоемкая операция, и если ваш numParts большой, это может иметь огромное влияние. Взгляните на этот вопрос.

person Malife    schedule 05.09.2012