使用 -fopenmp 标志编译时 MPI 代码要慢得多(对于具有多线程的 MPI)

MPI code much slower when compiling with -fopenmp flag (for MPI with multi-thread)

我用 mpif90 编译器用两个不同的 makefile 编译了一个 Fortran 90 代码,第一个看起来像;

FC = mpif90
FFLAGS = -Wall -ffree-line-length-none 
FOPT = -O3

all: ParP2S.o ParP2S
ParP2S.o: ParP2S.f90
        $(FC) $(FFLAGS) $(FOPT) ParP2S.f90 -c
ParP2S: ParP2S.o
        $(FC) $(FFLAGS) $(FOPT) ParP2S.o -o ParP2S
clean: 
        rm -f *.o* rm -f *.o* 

第二个 makefile 看起来很相似,我只是添加了 -fopenmp 标志;

FC = mpif90
FFLAGS = -Wall -ffree-line-length-none -fopenmp
FOPT = -O3

all: ParP2S.o ParP2S
ParP2S.o: ParP2S.f90
        $(FC) $(FFLAGS) $(FOPT) ParP2S.f90 -c
ParP2S: ParP2S.o
        $(FC) $(FFLAGS) $(FOPT) ParP2S.o -o ParP2S
clean: 
        rm -f *.o* rm -f *.o* 

第二个 makefile 用于代码的混合(MPI 和 OpenMP)版本。现在,我有完全相同的代码,但使用这些不同的 makefile 进行了编译。在第二种情况下,代码慢了 100 多倍。对我做错了什么有意见吗?

编辑 1:我不是 运行ning 多线程任务。事实上,该代码没有任何 OpenMP 指令,它只是纯 MPI 代码,但使用不同的 makefile 编译。尽管如此,我确实在执行了以下命令(见下文)后尝试 运行ning,但没有帮助。

export MV2_ENABLE_AFFINITY=0
export OMP_NUM_THREADS=1
export OMP_PROC_BIND=true
mpirun -np 2 ./ParP2S

编辑 2:我使用的是 gcc 4.9.2 版(我知道在旧版本中使用 fopenmp 进行矢量化存在一个错误)。我认为包含 -fopenmp 标志可能会抑制编译器优化,但是,在阅读了有趣的讨论 (May compiler optimizations be inhibited by multi-threading?) 之后,我不确定是否属于这种情况。此外,由于我的代码没有任何 OpenMP 指令,我不明白为什么使用 -fopenmp 编译的代码应该那么慢。

edit3:当我 运行 没有 -fopenmp(第一个 makefile)时,没有优化(-O0)大约需要 0.2 秒,优化(-O3)需要 0.08 秒,但包括标志 -fopenmp 它需要使用 -O3 或 -O0 大约需要 11 秒。

正如 Vladimir F 和 Gilles Gouaillardet 所建议的(非常感谢!),事实证明问题确实是任务亲和性。

首先我意识到我是 运行 MPI with OpenMPI version 1.6.4 而不是 MVAPICH2,所以命令 export MV2_ENABLE_AFFINITY=0 在这里没有实际意义。其次,我(大概)通过设置

来处理不同 OpenMP 线程的亲和力
export OMP_PROC_BIND=true
export OMP_PLACES=cores

但我没有为 MPI 进程设置正确的绑定,因为我错误地启动应用程序

mpirun -np 2 ./Par2S

似乎对于 OpenMPI 1.6.4 版,更合适的方法是

mpirun -np 2 -bind-to-core -bycore -cpus-per-proc 2  ./hParP2S

命令 -bind-to-core -bycore -cpus-per-proc 2 确保我的应用程序有 2 个内核(参见 https://www.open-mpi.org/doc/v1.6/man1/mpirun.1.php and also Gilles Gouaillardet's comments on )。没有它,两个 MPI 进程都将进入一个单核,这就是在 Makefile 中使用标志 -fopenmp 时代码效率低下的原因。

显然,当 运行 编译的纯 MPI 代码没有 -fopenmp 标志时,不同的 MPI 进程会自动进入不同的核心,但是 -fopenmp 需要手动指定绑定,如所述以上。

为了完整性,我应该提到没有设置正确任务亲和力的标准,所以我的解决方案不适用于例如MVAPICH2 或(可能)不同版本的 OpenMPI。此外,运行 nproc MPI 进程在 ncores 个 CPU 中每个具有 nthreads 需要例如

export OMP_PROC_BIND=true
export OMP_PLACES=cores
export OMP_NUM_THREADS=nthreads

mpirun -np nproc -bind-to-core -bycore -cpus-per-proc ncores ./hParP2S

其中 ncores=nproc*nthreads。

ps:我的代码有一个 MPI_all_to_all。多个 MPI 进程在一个单核(无超线程)上调用此子例程的情况应该是代码慢大约 100 倍的原因。