Этот код медленнее с OpenMP. Без OpenMP я получаю около 10 секунд. С OpenMP я получаю около 40 с. Что происходит? Спасибо большое друзья!
for (i=2;i<(nnoib-2);++i){
#pragma omp parallel for
for (j=2; j<(nnojb-2); ++j) {
C[i][j]= absi[i]*absj[j]*
(2.0f*B[i][j] + absi[i]*absj[j]*
(VEL[i][j]*VEL[i][j]*fat*
(16.0f*(B[i][j-1]+B[i][j+1]+B[i-1][j]+B[i+1][j])
-1.0f*(B[i][j-2]+B[i][j+2]+B[i-2][j]+B[i+2][j])
-60.0f*B[i][j]
)-A[i][j]));
c2 = (abs(C[i][j]) > Amax[i][j]);
if (c2) {
Amax[i][j] = abs(C[i][j]);
Ttra[i][j] = t;
}
}
}
То, что вы используете OpenMP, не означает, что ваша программа будет работать быстрее. Здесь может происходить несколько вещей:
Существует порода, связанная с порождением каждого потока, и если вы порождаете поток для выполнения небольшого количества вычислений, порождение самого потока займет больше времени, чем вычисление.
По умолчанию OpenMP создает максимальное количество потоков, поддерживаемых вашим ЦП. С процессорами, которые поддерживают 2 или более потоков на ядро, потоки будут конкурировать за ресурсы каждого ядра. С помощью omp_get_num_threads()
Вы можете видеть, сколько потоков будет порождено по умолчанию. Я рекомендую попробовать запустить ваш код с половиной этого значения, используя omp_set_num_threads()
,
Вы подтвердили, что результаты были одинаковыми с и без OpenMP? Кажется, есть зависимость с переменными j и c2. Вы должны объявить их закрытыми для каждого потока:
#pragma omp parallel for private(j,c2)
Я хотел добавить еще одну вещь: перед попыткой распараллеливания, вы должны убедиться, что код уже оптимизирован.
В зависимости от вашего компилятора, флагов компилятора и сложности инструкции, компилятор может оптимизировать или не оптимизировать ваш код:
// avoid calculation nnoib-2 every iteration
int t_nnoib = nnoib - 2;
for (i=2; i< t_nnoib; ++i){
// avoid calculation nnojb-2 every iteration
int t_nnojb = nnojb - 2;
// avoid loading absi[i] every iteration
int t_absi = absi[i];
for (j=2; j< t_nnojb; ++j) {
C[i][j]= t_absi * absj[j] *
(2.0f*B[i][j] + t_absi * absj[j] *
(VEL[i][j] * VEL[i][j] * fat *
(16.0f * (B[i][j-1] + B[i][j+1] + B[i-1][j] + B[i+1][j])
-1.0f * (B[i][j-2] + B[i][j+2] + B[i-2][j] + B[i+2][j])
-60.0f * B[i][j]
) - A[i][j]));
// c2 is a useless variable
if (abs(C[i][j]) > Amax[i][j]) {
Amax[i][j] = abs(C[i][j]);
Ttra[i][j] = t;
}
}
}
Это может показаться не очень много, но это может оказать огромное влияние на ваш код. Компилятор попытается поместить локальные переменные в регистры (которые имеют гораздо более быстрое время доступа). Имейте в виду, что вы не можете применять эту технику бесконечно, так как у вас ограниченное количество регистров, и злоупотребление этим может привести к тому, что ваш код будет страдать от проливания регистров.
В случае с массивом absi
вы избежите хранения системой части этого массива в кэше во время выполнения j
петля. Общая идея этого метода — переместить во внешний цикл любой доступ к массиву, который не зависит от переменной внутреннего цикла.
В дополнение к затратам, указанным Криштиану, ваш выбор распараллелить j
цикл, а не над i
цикл создает риск ложный обмен в трех назначаемых массивах, C, Amax, Ttra
, По сути, когда один поток выполняет запись в элемент одного из этих массивов, смежные элементы в одной и той же строке кэша также будут загружены в кэш этого ядра. Когда другое ядро затем отправляет свои собственные значения в другие записи, ему придется вытянуть строку из другого кэша, так как несколько ядер могут играть в «перетягивание каната».
Решением этой проблемы является распараллеливание внешнего цикла над i
вместо внутреннего цикла j
, Удобно, что это также значительно снижает затраты, упомянутые в ответе Криштиану, так как порождение и рабочие задания будут происходить только один раз, а не для каждой итерации в течение i
петля. Вам все еще нужно приватизировать j
а также c2
или просто укажите значение c2
в последующем if
и удалите переменную (как описано в вашем комментарии). Для большей эффективности, используя локально объявленные переменно вместо j
будет означать отсутствие доступа к частной переменной потока.
Являясь (довольно важной) проверкой, это гнездо цикла на самом деле является частью вашей программы, которую, как вы измеряли, занимают большую часть времени? Добавление прагмы OpenMP изменило свое время с чуть менее 10 с до чуть менее 40 с?