2014-04-26 3 views
0

Это часть кода C++ для решения проблемы в вычислительной математике большой размерности, скажем, более 100000 переменных. Я хотел бы его параллелизировать с помощью OpenMP. Каков наилучший способ сопоставления следующего вложенного цикла OpenMP?Как параллельно вложенному циклу

e = 0; 
// m and n are are big numbers 200000 - 10000000 
int i,k,r,s,t; 
// hpk,hqk,pk_x0,n2pk_x0,dk,sk are double and declared before. 
for (k=0; k<m; k++) 
{ 
    hpk  = 0;  
    hqk  = 0; 
    n2pk_x0 = 0; 
    dk  = 0; 
    sk  = 0; 

    for (int i=0; i<n; i++) 
    { 
    if (lamb[i] <= lam[k]) 
    { 
      if (h[i]<0) 
      { 
      pk[i] = xu[i]; 
      } 
      else if (h[i]>0) 
      { 
      pk[i] = xl[i]; 
      } 
      qk[i] = 0; 
    } 
    else 
    { 
      pk[i] = x0[i]; 
      qk[i] = -h[i]; 
    } 

    hpk  += h[i]*pk[i]; 
    hqk  += h[i]*qk[i]; 
    pk_x0 = pk[i]-x0[i]; 
    n2pk_x0 += pk_x0*pk_x0; 
    dk  += pk_x0*qk[i]; 
    sk  += qk[i]*qk[i]; 
    } 
    //}//p 

    /* ------- Compute ak, bk, ck, dk and sk to construct e(lam) -------- */ 
    ak = - (gamma + hpk); 
    bk = - hqk; 
    ck = q0 + 0.5 * n2pk_x0; 
    sk = 0.5 * sk; 
    // some calculation based on index k 
} // end of first for 

Я сделал некоторые из посоветует частных локальных переменных в вложенная loop.The процессорного времени снизился на коэффициент 1/2, но выход не правильно! Есть ли способ улучшить код таким образом, чтобы получить правильный результат с меньшим временем процессора? (В вложенном цикле, если положить т = 1, то выход будет правильным, но при т> 1 выход неправильно.)

Это весь код:

static void subboconcpp(
       double u[], 
       double *Egh, 
        double h[], 
       double gamma, 
        double x0[], 
       double q0, 
        double xl[], 
       double xu[], 
        int dim 
       ) 
{ 

int n,m,infinity = INT_MAX,i,k,r,s,t;; 
double e; 
double hpk, hqk, dk1, sk1, n2pk_x0; 
double ak, bk, ck, dk, sk; 
double lam_hat, phik, ek1, ek2; 
double *pk = new double[dim]; 
double *qk = new double[dim]; 
double *lamb = new double[dim]; 
double *lamb1 = new double[dim]; 
double *lam = new double[dim]; 

/* ------------------ Computing lambl(i) and lambu(i) ------------------ */ 
/* n is the length of x0 */ 
n = dim; 

#pragma omp parallel for shared(n,h,x0,xl,xu)//num_threads(8) 
for (int i=0; i<n; i++) 
{ 
    double lamb_flag; 
    if (h[i] > 0) 
    { 
      lamb_flag = (x0[i] - xl[i])/h[i]; 
      lamb[i] = lamb_flag; 
      lamb1[i] = lamb_flag; 
    } 
    else if (h[i] < 0) 
    { 
      lamb_flag = (x0[i] - xu[i])/h[i]; 
      lamb[i] = lamb_flag; 
      lamb1[i] = lamb_flag; 
    } 
    //cout << "lamb:" << lamb[i]; 
} 
/* --------------------------------------------------------------------- */ 

/* ----------------- Sorting lamb and constructing lam ----------------- */ 

/* lamb = sort(lamb,1); */ 
sort(lamb1, lamb1+n); 

int q  = 0; 
double lam_flag = 0; 
#pragma omp parallel for shared(n) firstprivate(q) lastprivate(m) 
for (int j=0; j<n; j++) 
{ 
    if (lamb1[j] > lam_flag) 
    { 
    lam_flag = lamb1[j]; 
    q  = q + 1; 
    lam[q] = lam_flag; 
    //cout << "lam: \n" << lam[q]; 
    } 

    if (j == n-1) 
    { 
    if (lam_flag < infinity) 
    { 
     m = q+1; 
     lam[m] = + infinity; 
    } 
    else 
    { 
     m = q; 
    } 
    } 
    //cout << "q: \n" << q; 
} 

/* --------------------------------------------------------------------- */ 

/* -- Finding the global maximizer of e(lam) for lam in[-inf, + inf] -- */ 
e = 0; 

#pragma omp parallel shared(m,n,h,x0,xl,xu,lamb,lam) \ 
private(i,r,s,t,hpk, hqk, dk1, sk1, n2pk_x0,ak, bk, ck, dk, sk,lam_hat, phik, ek1, ek2) 
{ 
#pragma omp for nowait 

for (k=0; k<1; k++) 
{ 
    /*double hpk=0, hqk=0, dk1=0, sk1=0, n2pk_x0=0; 
    double ak, bk, ck, dk, sk; 
    double lam_hat, phik, ek1, ek2; 
    double *pk = new double[dim]; 
    double *qk = new double[dim];*/  

    hpk  = 0;  
    hqk  = 0; 
    n2pk_x0 = 0; 
    dk1  = 0; 
    sk1  = 0; 

    for (int i=0; i<n; i++) 
    { 
    double pk_x0; 
    if (lamb[i] <= lam[k]) 
    { 
      if (h[i]<0) 
      { 
      pk[i] = xu[i]; 
      } 
      else if (h[i]>0) 
      { 
      pk[i] = xl[i]; 
      } 
      qk[i] = 0; 
    } 
    else 
    { 
      pk[i] = x0[i]; 
      qk[i] = -h[i]; 
    } 

    hpk  += h[i]*pk[i]; 
    hqk  += h[i]*qk[i]; 
    pk_x0 = pk[i]-x0[i]; 
    n2pk_x0 += pk_x0*pk_x0; 
    dk1  += pk_x0*qk[i]; 
    sk1  += qk[i]*qk[i]; 
    } 

    /* ------- Compute ak, bk, ck, dk and sk to construct e(lam) -------- */ 
    ak = - (gamma + hpk); 
    bk = - hqk; 
    ck = q0 + 0.5 * n2pk_x0; 
    dk = dk1; 
    sk = 0.5 * sk1; 
    /* ----------------------------------------------------------------- */ 

    /* - Finding the global maximizer of e(lam) for [lam(k), lam(k+1)] - */ 
    /* --------------------- using Proposition 4 ----------------------- */ 
    if (bk != 0) 
    { 
     double w = ak*ak - bk*(ak*dk - bk*ck)/sk; 
     if (w == 0) 
     { 
       lam_hat = -ak/bk; 
       phik = 0; 
     } 
     else 
     { 
       double w = ak*ak - bk*(ak*dk - bk*ck)/sk; 
       lam_hat = (-ak + sqrt(w))/bk; 
       phik = bk/(2*sk*lam_hat + dk); 
     } 
    } 
    else 
    { 
     if (ak > 0) 
     { 
        lam_hat = -dk/(2 * sk); 
        phik = 4*ak*sk/(4*ck*sk + (sk - 2)*(dk*dk)); 
     } 
     else 
     { 
        lam_hat = + infinity; 
        phik = 0; 
     } 
    } 
    /* ----------------------------------------------------------------- */ 

    /* --- Checking the feasibility of the solution of Proposition 4 --- */ 
    if (lam[k] <= lam_hat && lam_hat <= lam[k + 1]) 
    { 
     if (phik > e) 
     { 
      for (r=0; r<n; r++) 
      { 
       u[r] = pk[r] + lam_hat * qk[r]; 
      } 

      e = phik; 
     } 
    } 
    else 
    { 
     ek1 = (ak+bk*lam[k])/(ck+(dk+sk*lam[k])*lam[k]); 
     ek2 = (ak+bk*lam[k+1])/(ck+(dk+sk*lam[k+1])*lam[k+1]);  
     if (ek1 >= ek2) 
     { 
       lam_hat = lam[k]; 
       if (ek1 > e) 
       { 
        for (s=0; s<n;s++) 
        { 
         u[s] = pk[s] + lam_hat * qk[s]; 
        } 

        e = ek1; 
       } 
     } 
     else 
     { 
       lam_hat = lam[k + 1]; 
       if (ek2 > e) 
       { 
        for (t=0; t<n;t++) 
        { 
         u[t] = pk[t] + lam_hat * qk[t]; 
        } 

        e = ek2; 
       } 
     } 
    } 
    /* ------------------------------------------------------------------ */ 

}/* ------------------------- End of for (k) --------------------------- */ 
}//p 
/* --------- The global maximizer by searching all m intervals --------- */ 
*Egh = e; 
delete[] pk; 
delete[] qk; 
delete[] lamb1; 
delete[] lamb; 
delete[] lam; 

return; 
/* --------------------------------------------------------------------- */ 

} 

Пожалуйста, обратите внимание, что первые два параллельных кода работают хорошо, но только вывод вложенного цикла верен.

Любое предложение или комментарий оценены.

ответ

1

Самый внешний цикл: я не знаю всего кода, но он выглядит так, что переменные hpk, hqk, n2pk_x0, dk, sk должны быть частными. Если вы не укажете, что они будут частными, это нарушит правильность.

OpenMP не всегда очень хорош для вложенного параллелизма. Это зависит от настроек OpenMP, но вложенный цикл может создавать потоки p * p, где p - это параллелизм вашего компьютера по умолчанию. Таким образом, большая переподписка может привести к существенному ухудшению производительности. В большинстве случаев Ok следует параллелизовать самому внешнему циклу и оставлять вложенные петли последовательными.

Одной из причин параллелизации вложенных циклов является улучшение балансировки работы. Но ваше дело, похоже, имеет сбалансированную работу, и вы не должны сталкиваться с проблемой балансировки работы, если вы распараллеливаете только самый внешний цикл.

Но если вы все еще хотите параллелизовать обе петли, я могу предложить использовать Intel TBB вместо OpenMP? Вы можете использовать tbb :: parallel_for для самого внешнего цикла и tbb :: parallel_reduce для вложенного. Intel TBB использует один пул потоков для всех своих алгоритмов, поэтому он не приведет к тому, что ваше приложение получит дополнительную подписку.

[обновлено] Некоторые распараллеливание советует:

  1. Пока вы не добьетесь правильности время выполнения ничего не значит.Поскольку исправление правильности может значительно изменить его (даже в некоторых случаях лучше);
  2. Не пытайтесь параллелизировать «все и сразу»: попытайтесь выполнить параллельный цикл. Будет легче понять, когда нарушена правильность;
  3. Не изменяйте совместно используемые общие переменные. Если вам это действительно нужно, вы должны пересмотреть ваш алгоритм и использовать специальные конструкции, такие как сокращения, атомные операции, блокировки/мьютексы/семафоры и т. Д.
  4. Будьте точны при записи в совместно используемых массивах с частными модифицированными индексами, так как разные потоки могут иметь одинаковые индексы.
+0

Спасибо, Алекс за подробный комментарий. Как вы пожелаете, я закрываю переменные вложенного цикла как обновленные в вопросе. Время CPU уменьшилось на коэффициент 1/2, но результат неправильный! Есть ли способ улучшить код, чтобы получить правильный результат с меньшим временем Cpu? – user3565262

+0

Я не проверил весь код, но у вас есть пара вопросов в разделе «Сортировка ягненка и построения лам»: 1. lam_flag является общим и вы изменяете его одновременно; 2. Несмотря на то, что q является частным, у вас одинаковые значения для разных потоков, это означает, что вы получаете доступ к одной и той же памяти с помощью lam [q]; 3. Даже серийная версия имеет проблемы: в худшем случае вы можете получить доступ к границам массива «lam». Выражения «lam [q] = lam_flag» и «lam [m] = + бесконечность» неверны, поскольку q теоретически может быть «n + 1», а m может быть «n + 1» и/или «n + 2». – Alex

0

Я думаю, что ваша идея Вложенные в таблицу сравнения не очень хорошо подбирают мышление OpenMP. Несмотря на то, что в OpenMP может быть реализован вложенный параллелизм, он приносит больше осложнений, чем необходимо. Как правило, в OpenMP вы только параллельны одному циклу одновременно.

Параллелизация должна выполняться на уровне с наименьшими зависимостями чередования. Часто это выходит на верхний уровень. В вашем конкретном случае это также верно, так как шаги во внешнем цикле не сильно связаны.

Я не знаю, что остальная часть кода делает, особенно то, что происходит со значениями hpk, hqk, n2pk_x0, dk и sk. Все, что вам нужно сделать, это добавить #pragma omp parallel for в свой код.

+0

Благодарим за отзыв. Фактически я делал некоторые попытки с «#pragma omp parallel for», но это просто уменьшает время работы на коэффициент 1/2, которого недостаточно для решения моей проблемы. Например, если я добавлю «#pragma omp parallel for reduction (+: hpk, hqk, n2pk_x0, dk, sk) private (pk_x0)» перед внутренним циклом, это просто уменьшает время работы на коэффициент 1/2. И если я добавлю «#pragma omp parallel для private (i)» перед первым циклом, время работы увеличилось в 2-4 раза (!!!) с неправильным решением. Есть ли какие-либо предложения по улучшению? Также будут удалены hpk, hqk, n2pk_x0, dk и sk. – user3565262

+0

@ user3565262, пожалуйста, напишите полный рабочий тест, чтобы мы могли дополнительно проанализировать ваш код. Какая переменная содержит * результат (ы) *? – niklasfi

+0

Спасибо Niklas за комментарий. Код является частью файла mex, который будет использоваться в Matlab, поэтому в вопрос добавляется вычислительная рутина. (второй код немного обновлен относительно первого). Вывод - это скалярные массивы Egh и n * 1 u. – user3565262

Смежные вопросы