Ответ 1
Золотое правило OpenMP заключается в том, что все переменные (с некоторыми исключениями), которые определены во внешней области, по умолчанию делятся в параллельной области. Поскольку в Fortran до 2008 года нет локальных областей (т.е. В более ранних версиях нет BLOCK ... END BLOCK
), все переменные (кроме threadprivate
) разделяются, что очень естественно для меня (в отличие от Иана Буша, я не являюсь большой поклонник использования default(none)
, а затем обновляя видимость всех 100 + локальных переменных в различных сложных научных кодах).
Вот как определить класс совместного доступа для каждой переменной:
-
N
- общий, поскольку он должен быть одинаковым во всех потоках, и они только читают его значение. -
ii
- это счетчик цикла, в соответствии с директивой о коллективной работе, поэтому его класс совместного использования предопределен бытьprivate
. Это не помешает явно объявить его в предложенииprivate
, но это действительно не нужно. -
jj
- счетчик циклов цикла, который не подпадает под действие директивы о работе, поэтомуjj
должен бытьprivate
. -
X
- общий, поскольку все потоки ссылаются и только считываются с него. -
distance_vector
- очевидно, должен бытьprivate
, поскольку каждый поток работает на разных парах частиц. -
distance
,distance2
иcoff
- то же самое. -
M
- должны использоваться по тем же причинам, что иX
. -
PE
- действует как переменная аккумулятора (я думаю, что это потенциальная энергия системы) и должна быть объектом операции сокращения, то есть должна быть помещена в предложениеREDUCTION(+:....)
. -
A
- эта сложная задача. Он может быть либо общим, либо обновляться доA(jj,:)
, защищенным конструкциями синхронизации, или вы можете использовать сокращение (OpenMP допускает сокращение по переменным массива в Fortran, в отличие от C/С++).A(ii,:)
никогда не изменяется более чем одним потоком, поэтому ему не нужна специальная обработка.
С уменьшением на A
на месте каждый поток получит свою личную копию A
, и это может быть память-свиньи, хотя я сомневаюсь, что вы использовали бы это прямое O (N 2) для вычисления систем с очень большим числом частиц. Также есть определенные накладные расходы, связанные с реализацией сокращения. В этом случае вам просто нужно добавить A
в список предложения REDUCTION(+:...)
.
С синхронизирующими конструкциями у вас есть два варианта. Вы можете использовать конструкцию ATOMIC
или конструкцию CRITICAL
. Поскольку ATOMIC
применим только к скалярным контекстам, вам придется "развить" цикл назначения и применять ATOMIC
к каждому оператору отдельно, например:
!$OMP ATOMIC UPDATE
A(jj,1)=A(jj,1)+(M(ii)/coff)*(distance_vector(1))
!$OMP ATOMIC UPDATE
A(jj,2)=A(jj,2)+(M(ii)/coff)*(distance_vector(2))
!$OMP ATOMIC UPDATE
A(jj,3)=A(jj,3)+(M(ii)/coff)*(distance_vector(3))
Вы также можете переписать это как цикл - не забудьте объявить счетчик циклов private
.
С CRITICAL
нет необходимости разворачивать цикл:
!$OMP CRITICAL (forceloop)
A(jj,:)=A(jj,:)+(M(ii)/coff)*(distance_vector)
!$OMP END CRITICAL (forceloop)
Именование критических областей является необязательным и немного ненужным в данном конкретном случае, но в целом позволяет отделить не связанные критические регионы.
Что быстрее? Развернуто с помощью ATOMIC
или CRITICAL
? Это зависит от многих вещей. Обычно CRITICAL
является более медленным, поскольку он часто включает вызовы функций в среду выполнения OpenMP, в то время как атомные приращения, по крайней мере на x86, реализуются с помощью инструкций с добавлением с добавлением. Как они часто говорят, YMMV.
Чтобы повторить, рабочая версия вашего цикла должна выглядеть примерно так:
!$OMP PARALLEL DO PRIVATE(jj,kk,distance_vector,distance2,distance,coff) &
!$OMP& REDUCTION(+:PE)
do ii=1,N-1
do jj=ii+1,N
distance_vector=X(ii,:)-X(jj,:)
distance2=sum(distance_vector*distance_vector)
distance=DSQRT(distance2)
coff=distance*distance*distance
PE=PE-M(II)*M(JJ)/distance
do kk=1,3
!$OMP ATOMIC UPDATE
A(jj,kk)=A(jj,kk)+(M(ii)/coff)*(distance_vector(kk))
end do
A(ii,:)=A(ii,:)-(M(jj)/coff)*(distance_vector)
end do
end do
!$OMP END PARALLEL DO
Я предположил, что ваша система 3-мерная.
Со всем этим сказал, что я второй Иан Буш, вам нужно переосмыслить, как складываются матрицы положения и ускорения в памяти. Правильное использование кеша может повысить ваш код и также позволит выполнять определенные операции, например. X(:,ii)-X(:,jj)
для векторизации, то есть с использованием векторных инструкций SIMD.