Фортран OpenMP расчет для частичных сумм - PullRequest
1 голос
/ 28 марта 2019

Я очень плохо знаком с Фортраном и выполняю упражнение, которое включает параллельное суммирование чисел через OpenMP.

Мне сообщили, что следующий код правильно вычисляет сумму чисел параллельно через OpenMP

!$omp parallel do private (I)
!$omp+ reduction(+:totals)
do I=1,100
    totals = totals + localsum(I)
enddo
!$omp end parallel do

Если я настрою приведенный выше код так, чтобы я мог запустить его в своей собственной программе на Фортране, я получу

Program test
    implicit none
    real totals
    double precision, dimension (1 : 100) :: localsum
    integer I

    !$omp parallel do private (I)
    !$omp+ reduction(+:totals)
    do I=1,100
        localsum(I)=I
        totals = totals + localsum(I)
    enddo
    !$omp end parallel do
    print *, 'The calculated sum total is', totals
end

Эта программа возвращает

The calculated sum total is   5050.00000

Однако я не уверен, почему мне нужно было добавить дополнительную строку для

localsum(I)=I

когда в исходном коде не было этой строки. Я замечаю, что если я удалю

!$omp+ reduction(+:totals)

Тогда

Program test
    implicit none
    real totals
    double precision, dimension (1 : 100) :: localsum
    integer I

    !$omp parallel do private (I)
    do I=1,100
        localsum(I)=I
        totals = totals + localsum(I)
    enddo
    !$omp end parallel do
    print *, 'The calculated sum total is', totals
end

возвращается

 The calculated sum total is   5050.00000

когда вычисленная сумма должна быть неправильной. Включая сокращение, !$omp+ reduction(+:totals), необходимо для вычисления правильных итогов.

Есть ли альтернативный способ настройки цикла do в соответствии с предоставленным исходным кодом? Я не уверен, почему я должен был изменить

do I=1,100
    totals = totals + localsum(I)
enddo

до

do I=1,100
    localsum(I)=I
    totals = totals + localsum(I)
enddo

для расчета локальной суммы.

Ответы [ 2 ]

3 голосов
/ 28 марта 2019

Это дубликат Странные результаты с сокращением! $ Omp в Fortran OpenMP .

Согласно спецификации OpenMP (см. Стр. 42), правильным продолжением директивы OpenMP является использование & в конце предыдущей строки и !$omp& в строке продолжения (амперсанд в !$omp& равен необязательный). Итак, ваш код должен выглядеть так:

Program test
    implicit none
    real totals
    integer I
    integer, dimension(100) :: localsum
    !$omp parallel do private (I) &
    !$omp& reduction(+:totals)
        do I=1,100
            localsum(I)=I
            totals = totals + localsum(I)
        enddo
    !$omp end parallel do
    print *, 'The calculated sum total is', totals
end

Поскольку компилятор проигнорировал строку продолжения, определяющую сокращение по переменной totals, вы получили произвольный результат. При добавлении правильного продолжения я получаю правильный результат:

The calculated sum total is   5050.000
0 голосов
/ 28 марта 2019

С или без !$omp+ reduction(+:totals) исполняемый код отличается.

Без этой директивы вы напрямую обновляете глобальную переменную totals. Это может сработать (и в вашем примере это сработало), но это далеко не гарантировано. Проблема в том, что это может привести к гонкам.

Предположим, что поток a и поток b хотят обновить эту переменную. Им нужно:
1. извлечь переменную из памяти
2. обновить его в процессоре
3. записать его обратно в память

Каков будет относительный порядок этих операций в потоках a и b? Это не указано.
Если порядок 1a2a3a1b2b3b, проблем нет.
Если это 1a1b2a2b3a3b, то возникнет проблема: 1a1b (потоки a и b получают одно и то же значение) 2a2b (они обновляют его более или менее одновременно) 3a3b (поток a записывает свой результат, и он перезаписывается значением потока b).

Чтобы избежать этого, у вас могут быть атомарные операции, которые гарантируют, что цикл чтения-изменения-записи не может быть прерван, но они очень дороги и могут значительно замедлить время выполнения.

Чтобы избежать этого, вы должны использовать сокращения. Строка !$omp+ reduction(+:totals) говорит openmp сделать сокращение безопасным и эффективным способом. Что на самом деле будет сделано, это

  1. установить скрытый локальный var для накопления в частичном цикле
  2. на каждой итерации цикла выполняйте накопление в этой локальной переменной
  3. в конце накапливает эти частичные результаты в глобальную переменную totals безопасным способом: атомарная операция будет выполняться таким образом, что глобальная переменная будет корректно обновлена ​​и избежать гонок между потоками.

Есть еще атомарные обновления, но их количество уменьшается, и накопление в основном выполняется быстрыми локальными операциями.

Что касается полезности строки localsum(I)=I, то требуется, чтобы вектор localsum ранее не инициализировался. Но если цель состоит в том, чтобы просто добавить первые целые числа, вы можете просто использовать

do I=1,100
    totals = totals + I
enddo

Производительность будет улучшена, а результат идентичен. И оба цикла распараллелены одинаково.

...