Состояние гонки OpenMP (Fortran 77 с блоком COMMON)

Я пытаюсь распараллелить некоторый устаревший код Fortran с OpenMP. Проверяя условия гонки с помощью Intel Inspector, я столкнулся с проблемой в следующем коде (упрощенный проверенный пример):

        PROGRAM TEST

!$      use omp_lib
        implicit none
        DOUBLE PRECISION :: x,y,z
        COMMON /firstcomm/ x,y,z
!$OMP THREADPRIVATE(/firstcomm/)

        INTEGER :: i
!$      call omp_set_num_threads(3)       

!$OMP PARALLEL DO
!$OMP+ COPYIN(/firstcomm/)
!$OMP+ PRIVATE(i)
        do i=1,3000
            z = 3.D0
            y = z+log10(z)
            x=y+z
        enddo
!$OMP END PARALLEL DO

        END PROGRAM TEST

Intel Inspector обнаруживает состояние гонки между следующими строками:

  • !$OMP PARALLEL DO (прочитано)
  • z = 3.D0 (написать)

Представление Inspector "Disassembly" предлагает следующее о двух строках соответственно (я в них мало что понимаю, не считая того, что адреса памяти в обеих строках кажутся разными):

  • 0x3286 callq 0x2a30 <memcpy>
  • 0x3338 movq %r14, 0x10(%r12)

Как и в моем основном приложении, проблема возникает для одной (/some) переменной в общем блоке, но не для других, которые обрабатываются таким же образом.

Может ли кто-нибудь заметить мою ошибку, или это состояние гонки является ложным срабатыванием?

Я знаю, что использование ОБЩИХ блоков, как правило, не рекомендуется, но я не могу изменить это для текущего проекта.


person marcus_a    schedule 25.01.2016    source источник
comment
Какой компилятор Fortran вы используете?   -  person Hristo Iliev    schedule 26.01.2016
comment
Хотя я не являюсь экспертом по OPENMP, я рекомендую поместить предложение THREADPRIVATE непосредственно в переменные x,y,z, а не в общее имя блока firstcomm. Я думаю, что реализации openmp могут не проверять, находится ли переменная в блоке common, а затем генерировать ложное срабатывание предупреждения там, где его быть не должно. Это единственная проблема, которую я вижу в вашем коде.   -  person innoSPG    schedule 26.01.2016
comment
@HristoIliev Я использую ifort 16.0.1. @innoSPG Интересная мысль. В моем случае я понимаю, что должен использовать !$OMP THREADPRIVATE в общем блоке(ах), так как моя реальная программа использует переменные в общем блоке в подпрограммах, вызываемых из параллельной области, не передавая их в качестве аргументов (код примера не отражает это, мой плохой), и размер PRIVATE является лексическим, только если он не передается функциям/подпрограммам в качестве аргумента. Интересно, что использование подхода PRIVATE приводит к быстрому сбою моей программы, что говорит о том, что исходный код на самом деле может быть правильным.   -  person marcus_a    schedule 26.01.2016


Ответы (1)


С технической точки зрения, код вашего примера неверен, так как вы используете COPYIN для инициализации копий threadprivate данными из неинициализированного COMMON BLOCK. Но это не причина гонки данных — добавление оператора DATA или простое присвоение x, y и z до того, как параллельная область не изменит результат.

Это либо (очень старая) ошибка компилятора Intel Fortran, либо Intel странно интерпретирует текст стандарта OpenMP (раздел 2.15.4.1 текущей версии):

Копия выполняется, как бы по заданию, после формирования команды и до начала выполнения связанного структурированного блока.

Intel реализует выделенный текст, вставляя memcpy в начале описанной процедуры. Другими словами:

!$OMP PARALLEL DO COPYIN(/firstcomm/)
do i = 1, 3000
   ...
end do
!$OMP END PARALLEL DO

становится (в смеси Фортрана и псевдокода):

par_region0:
   my_firstcomm = get_threadprivate_copy(/firstcomm/)
   if (my_firstcomm != firstcomm) then
      memcpy(my_firstcomm, firstcomm, size of firstcomm)
   end if
   // Actual implementation of the DO worksharing construct
   call determine_iterations(1, 3000, low_it, high_it)
   do i = low_it, high_it
     ...
     ... my_firstcomm used here instead of firstcomm
     ...
   end do
   call openmp_barrier
end par_region0

MAIN:
   // Prepare a parallel region with 3 threads
   // and fire the outlined code in the worker threads
   call start_parallel_region(3, par_region0)
   // Fire the outlined code in the master thread
   call par_region0
   call end_parallel_region

Описанная процедура сначала находит адрес частной копии общего блока, а затем сравнивает этот адрес с адресом самого общего блока. Если оба адреса совпадают, то код выполняется в главном потоке и копия не требуется, в противном случае вызывается memcpy для создания побитовой копии данных мастера в блоке threadprivate.

Теперь можно было бы ожидать, что в конце части инициализации и прямо перед началом цикла должен быть барьер, и хотя Сотрудники Intel утверждают, что он есть, но его нет (проверено с ifort 11.0, 14.0 и 16.0). Более того, компилятор Intel Fortran не учитывает список переменных в предложении COPYIN и копирует весь общий блок, если какая-либо содержащаяся в нем переменная указана в предложении, т. е. COPYIN(x) обрабатывается так же, как COPYIN(/firstcomm/).

Являются ли это ошибками или особенностями компилятора Intel Fortran Compiler, только Intel может сказать. Также может быть, что я неправильно читаю вывод сборки. Если кто-нибудь сможет найти отсутствующий барьер, сообщите мне об этом. Одним из возможных обходных путей может быть разделение комбинированной директивы и вставка явного барьера перед конструкцией совместной работы:

!$OMP PARALLEL COPYIN(/firstcomm/) PRIVATE(I)
!$OMP BARRIER
!$OMP DO
      do i = 1, 3000
         z = 3.D0
         y = z+log10(z)
         x = y+z
      end do
!$OMP END DO
!$OMP END PARALLEL

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

GCC реализует COPYIN по-другому. Он создает общую копию данных threadprivate главного потока, которая копирует их, а затем передает рабочим потокам для использования в процессе копирования.

person Hristo Iliev    schedule 26.01.2016
comment
@SteveLionel ошибка/функция? - person Vladimir F; 26.01.2016
comment
Спасибо. Предложенное вами решение действительно привело к исчезновению состояния гонки и появлению нового состояния (чтение/запись и запись/запись) на y = z+log10(z). Не могли бы вы уточнить, почему вы ожидали этого? Как я могу убедиться, что эта гонка данных является ложным срабатыванием? На самом деле я также обнаруживаю, казалось бы, необъяснимую гонку данных в строке, вызывающей log10 в моем основном приложении (которое я пытался воссоздать в примере, но сначала это не удалось). Представление Inspector Disassembly тестовой программы предлагает точно такой же вывод для двух потоков, включая области памяти. - person marcus_a; 26.01.2016
comment
Я этого не ожидал. На самом деле я ожидал беспроблемного выполнения, но затем Intel Inspector начал обнаруживать гонку. Я предполагаю, что первый вызов log10 инициирует определение типа ЦП и выбор наиболее подходящей версии функции, после чего следует исправление батута, используемого для вызова функции. Последнее, вероятно, и вызывает ошибку. У меня есть основания полагать, что это делается потокобезопасным способом и, следовательно, ошибка является ложным срабатыванием. - person Hristo Iliev; 26.01.2016