У меня есть отлично работающий код, скомпилированный с помощью 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% времени!)?
Любые предложения о том, что попробовать.
@Йоахим Спасибо. Немного удивлен этим предложением, поскольку я думал, что использование шести потоков для трех последовательных БПФ будет медленнее, чем два потока на каждое БПФ, выполняемое одновременно. Полагаю, я мог бы сложить три БПФ (FFTW позволяет это), а затем просто задействовать все 6 потоков.
При проведении бенчмаркинга также имейте в виду, что создание планов fftw с включенными потоками занимает намного больше времени, поскольку оно измеряет производительность. Посмотрите, поможет ли хранение и загрузка файлов мудрости.
@Homer512 Спасибо. Да, у меня мудрость реализована. Однако я использую чип M1 Pro, который, по утверждению FFTW, не имеет синхронизации, поэтому он возвращается к ESTIMATE. Производственный код, вероятно, будет работать на x86, где будет работать мудрость. Хотелось бы понять узкое место и как его обойти. Завтра я попробую собрать трехмерные массивы и посмотреть, не будут ли они потреблять все шесть потоков.
Я скопировал код и провел несколько тестов. В моей системе (Intel i7-11800H) распараллеливание не может превышать 150% ускорения по сравнению с однопоточным. Если я правильно интерпретирую отчеты perf
(менее 1 инструкции за цикл, большое количество промахов LLC), код просто привязан к пропускной способности памяти, а не к производительности процессора.
@Homer512 Спасибо. Неудивительно, что это связано с пропускной способностью памяти. Благодарим вас за воспроизведение и проведение тестов. Я пытался расположить матрицы так, чтобы промахи в кэше были минимальными (для FFTW это говорит о том, что вам нужно менять индексы матрицы, потому что в Фортране есть матрица на основе столбцов, а не строк C). Никогда внимательно не присматривался к тому, что FFTW делает для поддержки многопоточности.
С точки зрения 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
и т. д. хорошо задокументированы: ссылка
Это работает, однако мне нужно установить переменные среды OMP_MAX_ACTIVE_LEVELS=2
и OMP_NUM_THREADS=3,2
. На Mac это правильно создает планы FFTW с двумя потоками на FFTW. Когда код достигает части SECTIONS
, он сначала насыщает все шесть потоков, но затем вскоре заходит в тупик, и загрузка процессора кодом падает до нуля. Он не вылетает, он просто блокируется. У меня нет Linux-версии, но я установил WSL в Windows 11. При той же настройке заняты только три потока. Другие потоки приходят и уходят, но не выполняют никакой работы. попробую сложить.
Я попробовал перекомпилировать FFTW с -enable-threads (вместо -enable-openmp). Включена библиотека fftw_threads (вместо fftw_openmp) в основной код. Взломан, чтобы указать коду всегда использовать 2 потока для FFTW и выполнить один уровень OpenMP с тремя потоками. На Маке не работает. Он порождает шесть потоков. Но половина сидит в _pthread_cond_wait. Он никогда не завершает один проход через FFTW, когда его вызывают изSECTIONS
. Он завершается, если я вызываю FFTW вне какой-либо конструкции OpenMP. Раздражающий.
Согласно stackoverflow.com/a/15013395, создание плана не является потокобезопасным. Вы создаете план последовательно и выполняете его только параллельно?
Установка omp_num_threads=6 назначит 6 потоков внешней параллельной области. Большинство реализаций вложенного параллелизма, которые я видел, реализованы не очень хорошо. Большую часть времени потоки воссоздаются для каждой внутренней параллельной области. Я бы попытался выполнить вызовы fft последовательно и использовать все потоки для параллельного выполнения внутри fft.