Как использовать многопоточный FFTW внутри кода OpenMP на Фортране

У меня есть отлично работающий код, скомпилированный с помощью gcc/gfortran-14 (установленный через Brew). Подавляющее большинство времени тратится на БПФ через FFTW. В критической части кода у меня есть это:

!$omp parallel
!$omp sections
!$omp section
       call fft(xi3d,xo3d,.false.,xip,layered_p)
!$omp section
       call fft(yi3d,yo3d,.false.,yip,layered_p)
!$omp section
       call fft(zi3d,zo3d,.false.,zip,layered_p)
!$omp end sections
!$omp sections
!$omp section
       x3d=n3d(:,:,:,1,1)*xo3d+n3d(:,:,:,2,1)*yo3d+n3d(:,:,:,3,1)*zo3d
!$omp section
       y3d=n3d(:,:,:,4,1)*xo3d+n3d(:,:,:,5,1)*yo3d+n3d(:,:,:,6,1)*zo3d
!$omp section
       z3d=n3d(:,:,:,7,1)*xo3d+n3d(:,:,:,8,1)*yo3d+n3d(:,:,:,9,1)*zo3d
!$omp end sections
!$omp sections
!$omp section
       call fft(rx3d,x3d,.true.,xp,layered_p)
!$omp section
       call fft(ry3d,y3d,.true.,yp,layered_p)
!$omp section
       call fft(rz3d,z3d,.true.,zp,layered_p)
!$omp end sections
!$omp end parallel

где x/y/zi3d и rx/y/z3d — настоящие 3D-массивы, а все остальные — сложные 3D-массивы (все они созданы с помощью fftw_alloc_real/complex). Подпрограмма fft просто вызывает fftw_plan_dft_c2r_3d или fftw_plan_dft_r2c_3d в зависимости от третьего параметра (т. е. .true. или .false.).

Обычно я работаю с тремя потоками. поэтому БПФ выполняется параллельно. Это работает очень хорошо, и производительность кода действительно увеличивается почти в 3 раза.

Теперь я пытаюсь использовать OpenMP с самим FFTW. То есть скажите FFTW использовать 2 или 3 потока в каждом FFT вместо одного. Итак, в начале кода я выполняю необходимую инициализацию:

     if (fftw_init_threads().eq.0) then
       print *,'fftw has problem: fftw_init_threads'
     else
       call fftw_plan_with_nthreads(max(1,omp_get_max_threads()/3))
       print *,'each fftw will use ',max(1,omp_get_max_threads()/3),' threads'
   !   call omp_set_nested(.true.)
     endif

Однако, когда я запускаю код (даже после установки export OMP_NUM_THREADS=6 или export OMP_NUM_THREADS=3,2, код работает медленнее. Установка значения 6, top сообщает мне, что работают только три процессора. Другая настройка показывает, что работает 4,5 процессора, но в обоих случаях код работает медленнее. чем просто использовать 3 потока и не использовать FFTW OpenMP.

Размер массивов, которые я использую, составляет 512x512x256. У меня MacBookPro с M1Pro и 32 ГБ памяти.

Нужен ли мне omp_set_nested(.true.) или нет? Что я могу сделать, чтобы увидеть, как все 6 процессоров привязаны во время выполнения FFTW (что в этом коде занимает более 95% времени!)?

Любые предложения о том, что попробовать.

Установка omp_num_threads=6 назначит 6 потоков внешней параллельной области. Большинство реализаций вложенного параллелизма, которые я видел, реализованы не очень хорошо. Большую часть времени потоки воссоздаются для каждой внутренней параллельной области. Я бы попытался выполнить вызовы fft последовательно и использовать все потоки для параллельного выполнения внутри fft.

Joachim 25.08.2024 09:08

@Йоахим Спасибо. Немного удивлен этим предложением, поскольку я думал, что использование шести потоков для трех последовательных БПФ будет медленнее, чем два потока на каждое БПФ, выполняемое одновременно. Полагаю, я мог бы сложить три БПФ (FFTW позволяет это), а затем просто задействовать все 6 потоков.

user26999065 25.08.2024 09:20

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

Homer512 25.08.2024 09:29

@Homer512 Спасибо. Да, у меня мудрость реализована. Однако я использую чип M1 Pro, который, по утверждению FFTW, не имеет синхронизации, поэтому он возвращается к ESTIMATE. Производственный код, вероятно, будет работать на x86, где будет работать мудрость. Хотелось бы понять узкое место и как его обойти. Завтра я попробую собрать трехмерные массивы и посмотреть, не будут ли они потреблять все шесть потоков.

user26999065 25.08.2024 09:51

Я скопировал код и провел несколько тестов. В моей системе (Intel i7-11800H) распараллеливание не может превышать 150% ускорения по сравнению с однопоточным. Если я правильно интерпретирую отчеты perf (менее 1 инструкции за цикл, большое количество промахов LLC), код просто привязан к пропускной способности памяти, а не к производительности процессора.

Homer512 25.08.2024 17:05

@Homer512 Спасибо. Неудивительно, что это связано с пропускной способностью памяти. Благодарим вас за воспроизведение и проведение тестов. Я пытался расположить матрицы так, чтобы промахи в кэше были минимальными (для FFTW это говорит о том, что вам нужно менять индексы матрицы, потому что в Фортране есть матрица на основе столбцов, а не строк C). Никогда внимательно не присматривался к тому, что FFTW делает для поддержки многопоточности.

user26999065 25.08.2024 17:23
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
51
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

С точки зрения OpenMP вы хотите выполнить две вложенные параллельные области с тремя потоками на внешнем уровне, которые затем выполняют n потоков на внутреннем уровне. Начиная с OpenMP 5.0, использование nested-var и его API устарело и теперь контролируется либо установкой max-active-levels-var, либо элементами в списке nthreads-var (3,n в вашем случае). omp_get_max_threads() определяется для предоставления начального значения списка nthreads-var. Таким образом, из последовательного контекста он предоставит вам 3.

Таким образом, в своем коде вы либо указываете fftw планировать выполнение с двумя потоками (OMP_NUM_THREADS=6), но затем выполняете один поток, либо вы указываете fftw планировать выполнение с одним потоком (OMP_NUM_THREADS=3,2), а затем выполняете с двумя потоками.

Чтобы правильно спланировать выполнение fftw, сложнее всего получить доступ к n из списка. Вы можете создать параллельный регион, чтобы имитировать ваш регион из трех разделов и вытащить три из списка nthreads-var. На этом этапе вы можете получить доступ ко второму значению, используя omp_get_max_threads().

     if (fftw_init_threads().eq.0) then
       print *,'fftw has problem: fftw_init_threads'
     else
!$omp parallel
!$omp single
       call fftw_plan_with_nthreads(max(1,omp_get_max_threads()))
       print *,'each fftw will use ',max(1,omp_get_max_threads()),' threads'
!$omp end single
!$omp end parallel
     endif

GCC реализует вложенность на основе списка nthreads-var, по крайней мере, начиная с версии 9, поэтому вам не нужно дополнительно вызывать omp_set_nested

Спасибо. Очень хорошее объяснение вложенности. Я вижу, что моя мысленная модель создания потоков была ошибочной. Надеюсь, ваше решение сработает, поскольку его легко реализовать. Я попробую сделать это, а также объединить три матрицы, чтобы всегда оставаться на одном уровне распараллеливания. Для дальнейшего использования nthreads-var и т. д. хорошо задокументированы: ссылка

user26999065 25.08.2024 17:39

Это работает, однако мне нужно установить переменные среды OMP_MAX_ACTIVE_LEVELS=2 и OMP_NUM_THREADS=3,2. На Mac это правильно создает планы FFTW с двумя потоками на FFTW. Когда код достигает части SECTIONS, он сначала насыщает все шесть потоков, но затем вскоре заходит в тупик, и загрузка процессора кодом падает до нуля. Он не вылетает, он просто блокируется. У меня нет Linux-версии, но я установил WSL в Windows 11. При той же настройке заняты только три потока. Другие потоки приходят и уходят, но не выполняют никакой работы. попробую сложить.

user26999065 26.08.2024 16:06

Я попробовал перекомпилировать FFTW с -enable-threads (вместо -enable-openmp). Включена библиотека fftw_threads (вместо fftw_openmp) в основной код. Взломан, чтобы указать коду всегда использовать 2 потока для FFTW и выполнить один уровень OpenMP с тремя потоками. На Маке не работает. Он порождает шесть потоков. Но половина сидит в _pthread_cond_wait. Он никогда не завершает один проход через FFTW, когда его вызывают изSECTIONS. Он завершается, если я вызываю FFTW вне какой-либо конструкции OpenMP. Раздражающий.

user26999065 26.08.2024 17:40

Согласно stackoverflow.com/a/15013395, создание плана не является потокобезопасным. Вы создаете план последовательно и выполняете его только параллельно?

Joachim 27.08.2024 06:55

Другие вопросы по теме