Результаты параллельной программы с вложенными циклами отличаются от последовательной программы
Я хотел бы использовать OpenMP для этого однопоточного кода:
PROGRAM SINGLE
INTEGER, DIMENSION(30000)::SUMGRM
INTEGER, DIMENSION(90000)::GRI,H
REAL*8::HSTEP1X,HSTEP2X
REAL*8::TIME1,TIME2
!Just intiial value
DO I=1, 30000
SUMGRM(I)=I*3
END DO
DO I=1, 90000
GRI(I)=I
H(I)=0.5*I/10000
END DO
!Computing computer's running time (start) : for serial programming
CALL CPU_TIME(TIME1)
DO K=1, 50000
DO I=2, 30000
HSTEP1X=0.0
DO J=SUMGRM(I-1)+1, SUMGRM(I)-1
HSTEP2X=H(GRI(J))/0.99
HSTEP1X=HSTEP1X+HSTEP2X
END DO
HSTEP2X=H(GRI(SUMGRM(I)))/0.99
HSTEP1X=HSTEP1X+HSTEP2X
END DO
END DO
PRINT *, 'Results =', HSTEP1X
PRINT *, ' '
!Computing computer's running time (finish) : for serial programming
CALL CPU_TIME(TIME2)
PRINT *, 'Elapsed real time = ', TIME2-TIME1, 'second(s)'
END PROGRAM SINGLE
Как видите, основная проблема заключается в самой внутренней боковой петле (J
), который также является функцией большинства внешних боковых петель (I
). Я попытался распараллелить эту программу следующим образом:
PROGRAM PARALLEL
INTEGER, DIMENSION(30000)::SUMGRM
INTEGER, DIMENSION(90000)::GRI,H
REAL*8::HSTEP1X,HSTEP2X
REAL*8::TIME1,TIME2,OMP_GET_WTIME
INTEGER::Q2,P2
!Just intiial value
DO I=1, 30000
SUMGRM(I)=I*3
END DO
DO I=1, 90000
GRI(I)=I
H(I)=0.5*I/10000
END DO
!Computing computer's running time (start) : for parallel programming
TIME1= OMP_GET_WTIME()
DO K=1, 50000
!$OMP PARALLEL DO PRIVATE (HSTEP1X,Q2,P2)
DO I=2, 30000
HSTEP1X=0.0
Q2=SUMGRM(I-1)+1
P2=SUMGRM(I)-1
DO J=Q2, P2
HSTEP2X=H(GRI(J))/0.99
HSTEP1X=HSTEP1X+HSTEP2X
END DO
HSTEP2X=H(GRI(SUMGRM(I)))/0.99
HSTEP1X=HSTEP1X+HSTEP2X
END DO
!$OMP END PARALLEL DO
END DO
PRINT *, 'Results =', HSTEP1X
PRINT *, ' '
!Computing computer's running time (finish) : for parallel programming
TIME2= OMP_GET_WTIME()
PRINT *, 'Elapsed real time = ', TIME2-TIME1, 'second(s)'
END PROGRAM PARALLEL
я использую gfortran with -O3 -fopenmp
а затем экспортировать OMP_NUM_THREADS=...
Параллельная программа работает быстрее, но результат отличается от кода с одним потоком. По серийной программе я получил 12.1212
(который является правильным) и параллельно я получил 0.000
(должно быть что-то не так).
Что я сделал не так?
2 ответа
Во-первых, мы можем заметить, что по умолчанию вы, вероятно, обнаружите, что оба j
а также hstep2x
будут разделены между потоками. Я не думаю, что это действительно то, что вам нужно, поскольку это приведет к очень странному поведению, когда несколько потоков используют один и тот же индекс итерации, но пытаются перебрать разные диапазоны.
Далее отметим, что ваш серийный код на самом деле просто печатает результат для i=30000
итерация как значение hstep1x
сбрасывается в 0 в начале каждой итерации. Таким образом, чтобы получить "правильный" ответ в коде openmp, мы могли бы просто сосредоточиться на воспроизведении финальной итерации - это полностью сводит на нет смысл использования openmp здесь, я думаю. Я предполагаю, что это просто простой случай, который вы пытаетесь использовать, чтобы представить свою реальную проблему - я думаю, что вы, возможно, упустили некоторые из реальных проблем при создании этого.
Тем не менее приведенный ниже код выдает "правильный" ответ на моей машине. Я не уверен, насколько это гибко, но это работает здесь.
PROGRAM PARALLEL
INTEGER, DIMENSION(30000)::SUMGRM
INTEGER, DIMENSION(90000)::GRI,H
REAL*8::HSTEP1X,HSTEP2X
REAL*8::TIME1,TIME2,OMP_GET_WTIME
INTEGER::Q2,P2
!Just intiial value
DO I=1, 30000
SUMGRM(I)=I*3
END DO
DO I=1, 90000
GRI(I)=I
H(I)=0.5*I/10000
END DO
!Computing computer's running time (start) : for parallel programming
TIME1= OMP_GET_WTIME()
DO K=1, 50000
!$OMP PARALLEL DO PRIVATE (Q2,P2,J,HSTEP2X) DEFAULT(SHARED) LASTPRIVATE(HSTEP1X)
DO I=2, 30000
HSTEP1X=0.0
Q2= SUMGRM(I-1)+1
P2= SUMGRM(I)-1
DO J=Q2,P2
HSTEP2X=H(GRI(J))/0.99
HSTEP1X=HSTEP1X+HSTEP2X
END DO
HSTEP2X=H(GRI(SUMGRM(I)))/0.99
HSTEP1X=HSTEP1X+HSTEP2X
END DO
!$OMP END PARALLEL DO
END DO
PRINT *, 'Results =', HSTEP1X
PRINT *, ' '
!Computing computer's running time (finish) : for parallel programming
TIME2= OMP_GET_WTIME()
PRINT *, 'Elapsed real time = ', TIME2-TIME1, 'second(s)'
END PROGRAM PARALLEL
Я сделал три вещи здесь:
- Удостовериться
j
а такжеhstep2x
являются частными для каждой темы. - Явно объявлено поведение по умолчанию для совместного использования (здесь не нужно, но не важно).
- Указано что
hstep1x
являетсяlastprivate
, Это означает, что после выхода из параллельной области значениеhstep1x
это то, что взято из потока, который выполнил последнюю итерацию. (подробности см. здесь)
Вы пытались использовать
!$OMP PARALLEL DO DEFAULT(PRIVATE) REDUCTION(+:HSTEP1X)