О механизме, лежащем в основе параллелизации openmp - PullRequest
0 голосов
/ 09 июля 2020

Мой вопрос, связанный с этим сайтом https://curc.readthedocs.io/en/latest/programming/OpenMP-Fortran.html

Следующий код Fortran

PROGRAM Parallel_Ordered_Hello
USE OMP_LIB

INTEGER :: thread_id

!$OMP PARALLEL PRIVATE(thread_id)

    thread_id = OMP_GET_THREAD_NUM()
    DO i=0,OMP_GET_MAX_THREADS()
        PRINT *, "Hello from process: ", thread_id
    END DO

!$OMP END PARALLEL

END

приводит к выводу типа

 Hello from process:            0
 Hello from process:            0
 Hello from process:            0
 Hello from process:            0
 Hello from process:            0
 Hello from process:            1
 Hello from process:            1
 Hello from process:            1
 Hello from process:            2
 Hello from process:            2
 Hello from process:            2
 Hello from process:            2
 Hello from process:            2
 Hello from process:            3
 Hello from process:            3
 Hello from process:            3
 Hello from process:            3
 Hello from process:            3
 Hello from process:            1
 Hello from process:            1

Приведенный выше вывод выглядит как один поток за другим (не полностью, поток 1, кажется, разделен на две части). Следовательно, они выглядят как-то серийно. Что на самом деле делает openmp?

1 Ответ

3 голосов
/ 09 июля 2020

Большинство реализаций OpenMP (с некоторыми умными исключениями) делают то же самое - они выделяют каждую параллельную область в отдельной функции, а затем запускают ее в нескольких потоках.

PROGRAM Parallel_Ordered_Hello
USE OMP_LIB

INTEGER :: thread_id, j

j = 0

!$OMP PARALLEL PRIVATE(thread_id) SHARED(j)

    thread_id = OMP_GET_THREAD_NUM()
    DO i=0,OMP_GET_MAX_THREADS()
        PRINT *, "Hello from process: ", thread_id
    END DO
    
    !$OMP ATOMIC UPDATE
    j = j + thread_id

!$OMP END PARALLEL

END

становится чем-то вроде

PROGRAM Parallel_Ordered_Hello
USE OMP_LIB

TYPE omp_data
    INTEGER, POINTER :: j
END TYPE

INTEGER :: thread_id
INTEGER, TARGET :: j
TYPE(omp_data) :: omp_shared_data

j = 0

omp_shared_data%j => j
CALL _libomp_parallel(omp_func1, omp_shared_data)

END


SUBROUTINE omp_func1(omp_shared_data)
USE OMP_LIB

TYPE(omp_data) :: omp_shared_data
INTEGER :: thread_id, i

    thread_id = OMP_GET_THREAD_NUM()
    DO i=0,OMP_GET_MAX_THREADS()
        PRINT *, "Hello from process: ", thread_id
    END DO

    CALL _libomp_atomic_increment(omp_shared_data%j, thread_id)

END SUBROUTINE

Я немного увеличил сложность, добавив общую переменную, просто чтобы показать, как обрабатываются такие переменные. omp_func1 - это выделенное содержимое параллельной области. Она выполняется в нескольких потоках при вызове _libomp_parallel() (это не настоящая функция, а просто макет). Потоки выполняются одновременно, и нет абсолютно никакой гарантии относительно порядка, в котором они отображаются. gfortran принимают опцию -fdump-tree-all, которая заставляет их выгружать промежуточное представление кода на разных этапах компиляции в текстовые файлы, по одному для каждого этапа. Код похож на C и несколько труден для чтения, но он дает отличное понимание того, как G CC обрабатывает OpenMP. Особый интерес представляют файлы .omplower и .ompexp.

Обратите внимание, что в приведенном вами примере есть два случая неявных определений относительно i. Во-первых, его тип неявно определяется как INTEGER из его имени (начинается с i). Во-вторых, его атрибут совместного использования данных OpenMP предварительно определен как PRIVATE в соответствии с правилом, согласно которому последовательные l oop переменные являются частными в самой внутренней параллельной области или конструкции, создающей задачу. В целом это плохая практика программирования. Используйте и IMPLICIT NONE, чтобы запретить автоматическую c типизацию Fortran, и DEFAULT(none), чтобы запретить автоматические c правила атрибутов совместного использования данных OpenMP.

Кстати, сообщение должно фактически читать «Здравствуйте от резьба ... ".

...