Результаты параллельной программы с вложенными циклами отличаются от результатов последовательной программы

Я хотел бы использовать 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 (должно быть что-то не так).

Что я сделал не так?


person bestrong    schedule 20.07.2016    source источник
comment
Используйте тег ptag:fortran] для всех вопросов по Fortran. Добавьте тег версии, где необходимо различать. Обратите внимание на количество людей, подписавшихся на fortran и на fortran95 и вы хотите, чтобы больше людей увидело ваш вопрос, не так ли?   -  person Vladimir F    schedule 20.07.2016
comment
Почти верно сказать, что параллельные программы всегда дают результаты, отличные от их последовательных собратьев. Будьте конкретны в том, чем отличаются результаты, из различий можно многому научиться.   -  person High Performance Mark    schedule 20.07.2016
comment
Пожалуйста, старайтесь использовать заголовки, указывающие на вашу проблему, а не только на тему. Тема довольно хорошо определяется тегами.   -  person Vladimir F    schedule 20.07.2016
comment
@VladimirF: Что ты имеешь в виду? Вы хотите сказать, что я тоже хочу набрать популярность по количеству просмотров? Нет, я никогда не стремился получить больше просмотров моих вопросов, и меня это действительно не волнует. Я просто думал, что получу наиболее правильный ответ от некоторых экспертов. Я даже не знаю, в чем польза от количества просмотров, за исключением того, что у меня может быть больше шансов получить ответ от некоторых экспертов. Скажите, пожалуйста, запрещено ли на этом сайте больше тегов, как я? Если да, то я с удовольствием удалю его. Спасибо.   -  person bestrong    schedule 20.07.2016
comment
@HighPerformanceMark: Разница в том, что с помощью одной программы я получил 12,1212 (это верно), а с помощью параллельной программы я получил 0,000 (что, должно быть, что-то не так).   -  person bestrong    schedule 20.07.2016
comment
@VladimirF: Спасибо за предложение по поводу названия. Вы совершенно правы.   -  person bestrong    schedule 20.07.2016
comment
bob.bob.bob Как вы сказали, вы хотите, чтобы больше людей увидело ваш вопрос, чтобы иметь больше шансов, что кто-то даст вам хороший ответ. @VladimirF предлагал вам использовать более общий (и подходящий) тег fortran, поскольку за ним следует больше людей, чем fortran95 (по крайней мере, я так понял).   -  person d_1999    schedule 20.07.2016
comment
real*8 недействителен Фортран. Он никогда не был частью какого-либо стандарта ISO Fortran.   -  person jlokimlin    schedule 20.07.2016
comment
@d_1999: О, понял. Если так, извините. Я не знал раньше. В следующий раз я уделю больше внимания тэгам и названию. В любом случае, все критические замечания и предложения действительно ценятся. Большое спасибо.   -  person bestrong    schedule 21.07.2016
comment
@jlokimlin: я не совсем уверен, что real * 8 не является ISO-частью Fortran, но в моем случае это работает. Это только для определения двойной точности. Не могли бы вы объяснить мне, что вы имеете в виду под «недопустимым Fortran»? Спасибо.   -  person bestrong    schedule 21.07.2016
comment
@bob.bob.bob datatype*n является распространенным расширением Фортрана, т. е. официально не является частью языка, если используется с datatype, отличным от character. При применении к символьному типу создается массив из n символов (или строка из n символов). При применении к другому числовому типу он указывает размер хранилища в байтах. Указание размера хранилища создает непереносимые приложения. См.: stackoverflow.com/questions/838310/fortran-90-kind- параметр   -  person jlokimlin    schedule 21.07.2016


Ответы (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

Я сделал здесь три вещи:

  1. Убедитесь, что j и hstep2x являются частными для каждого потока.
  2. Явно объявлено поведение по умолчанию для совместного использования (здесь это не нужно, но неважно).
  3. Указано, что hstep1x равно lastprivate. Это означает, что после выхода из параллельной области значение hstep1x берется из потока, выполнившего последнюю итерацию. (подробнее см. здесь).
person d_1999    schedule 20.07.2016
comment
Да вы абсолютно правы. Это не реальный случай, а случай воображения, и уж точно [DO I=2, 30000] абсолютно не требуется. Я просто добавил его, чтобы понять, что параллельный код работает, а также [DO K=1, 50000] не требуется, чтобы увидеть репрезентативную разницу во времени процессора. Да, я забыл поставить [HSTEP1X] на [LASTPRIVATE]. Кстати код работает и на моей машине. Ваше объяснение было отличным. - person bestrong; 21.07.2016
comment
Ах да, я забыл прокомментировать, что [J] может быть опционально определен как [PRIVATE], поскольку он находится внутри цикла, но оба из [Q2] и [P2] должны быть сохранены как [PRIVATE]. - person bestrong; 21.07.2016

Вы пробовали использовать

!$OMP PARALLEL DO DEFAULT(PRIVATE) REDUCTION(+:HSTEP1X)                                                                                                                                     
person Holmz    schedule 22.07.2016
comment
Я думаю, что это не сработает, так как основная проблема здесь в том, что [HSTEP1X] имеет свое значение (ноль) перед циклом [DO J=Q2,P2]. Таким образом, если мы используем [DEFAULT(PRIVATE)], значение [HSTEP1X] останется неизменным, равным нулю. - person bestrong; 22.07.2016
comment
Возможно, вы правы, bob.bob.bob, этот gfortran кажется намного сложнее, чем ifort. Я установил его сегодня и боролся с различиями. В любом случае, давайте проигнорируем УМОЛЧАНИЕ... УМЕНЬШЕНИЕ, похоже, здесь нужно? - person Holmz; 23.07.2016
comment
Уточните, пожалуйста, что вы имеете в виду под "намного сложнее"? Поскольку по моему опыту я использовал Gfortran и у меня никогда не было серьезных проблем, даже когда я использовал новый Intel fortran 16 на прошлой неделе, я получил сообщение о том, что он не поддерживает файлы с форматом .F95 и более поздние версии. И, наконец, я должен изменить формат на .F90 (но со старым Intel Fortran 13, так как новый все еще не работал), и тогда это сработало.... Да, вы могли абсолютно игнорировать [DEFAULT (SHARED)] в данном случае, так как это не имеет значения. Это просто моя привычка, что предпочитаю писать ее полностью. - person bestrong; 23.07.2016
comment
Ну, я искал gfortran ewuivilent to intel's -diag-file и -vector-report, чтобы получить , так что намного сложнее означает, что я не мог сделать это за 1/2 часа по сравнению с годами на Intel. OpenMP, казалось, полагался на -O# для любой векторизации... И, следовательно, векторные сообщения, и я получал ошибки на !$OMP DO SIMD REDUCTION(+:K) - person Holmz; 24.07.2016