MPI_Igather 是线程安全的吗?
Is MPI_Igather thread-safe?
我正在尝试启动一系列 MPI_Igather 调用(来自 MPI 4 的非阻塞集体),做一些工作,然后每当 Igather 完成时,对该数据做更多的工作。
工作正常,除非我在每个 MPI 等级上从不同的线程 启动 Igather。
在那种情况下,即使我调用 MPI_Init_thread 以确保提供了 MPI_THREAD_MULTIPLE,我也经常遇到死锁。
非阻塞集合没有匹配发送和接收的标签,但我认为这是由 MPI_Request 对象处理的
与每个集体行动相关联?
我发现失败的最简单示例是:
- 给定 np 个 MPI 等级,每个等级都有一个长度为 np
的局部数组
- 为每个元素 i 启动一个 MPI_Igather,在进程 i 上收集这些元素。
- i-loop 使用 OpenMP 并行化
- 然后调用MPI_Waitall()完成所有通信。这是将 OMP_NUM_THREADS 设置为大于 1 的值时程序挂起的地方。
我做了这个程序的两个变体:igather_threaded.cpp(下面的代码),其行为如上所述,以及 igather_threaded_v2.cpp,它收集 MPI 等级 0 上的所有内容。这个版本不会死锁, 但数据也没有正确排序。
igather_threaded.cpp:
#include <iostream>
#include <memory>
#include <mpi.h>
#define PRINT_ARRAY(STR,PTR) \
for (int r=0; r<nproc; r++){\
if (rank==r){\
for (int j=0; j<nproc; j++){\
(STR) << (PTR)[j] << " ";\
}\
(STR) << std::endl << std::flush;\
}\
MPI_Barrier(MPI_COMM_WORLD);\
}
int main(int argc, char* argv[]) {
int provided;
MPI_Init_thread(&argc,&argv, MPI_THREAD_MULTIPLE, &provided);
if (provided!=MPI_THREAD_MULTIPLE){
std::cerr << "Your MPI installation does not provide threading level MPI_THREAD_MULTIPLE, required for this program." << std::endl;
MPI_Abort(MPI_COMM_WORLD, -1);
}
int rank, nproc;
MPI_Comm_rank(MPI_COMM_WORLD,&rank);
MPI_Comm_size(MPI_COMM_WORLD,&nproc);
std::unique_ptr<double[]> M1(new double[nproc]);
std::unique_ptr<double[]> M2(new double[nproc]);
for (int j=0; j<nproc; j++) M1[j] = rank*nproc+j+1;
PRINT_ARRAY(std::cout,M1);
std::unique_ptr<MPI_Request[]> requests(new MPI_Request[nproc]);
#pragma omp parallel for schedule(static) shared(requests)
for (int j=0; j<nproc; j++){
MPI_Igather(
M1.get()+j, 1, MPI_DOUBLE,
M2.get(), 1, MPI_DOUBLE, j,
MPI_COMM_WORLD, &requests[j]
);
}
MPI_Waitall(nproc,requests.get(),MPI_STATUSES_IGNORE);
if (rank==0) std::cout << " => "<<std::endl;
PRINT_ARRAY(std::cout,M2);
MPI_Finalize();
return 0;
}
我的问题是:我的代码在理论上是否正确,这是 OpenMPI(此处使用 4.0.3)中的错误,还是我遗漏了任何不允许启动 MPI_Igather 调用的内容
多线程?我也用 Intel MPI 尝试过,结果相似。
为了编译,我使用 OpenMPI 4.0.3(配置为支持 MPI_THREAD_MULTIPLE)、gcc 11.2.0 和命令行
> mpicxx -std=c++17 -fopenmp -o igather_threaded igather_threaded.cpp
到运行,我用
mpirun -np 4 --bind-to none env OMP_NUM_THREADS=4 env OMP_PROC_BIND=false ./igather_threaded
您可能需要增加 MPI 等级数 and/or OpenMP 线程,and/or 运行 多次,因为确切的行为是不确定的。
在你的方案中,因为集合体是在不同的线程中启动的,所以它们可以按任意顺序启动。拥有不同的请求不足以消除它们的歧义:MPI 坚持它们在每个进程上以相同的顺序启动。
您可以通过以下方式解决此问题:
#pragma omp parallel for schedule(static) shared(requests) ordered
for (int j=0; j<nprocs; j++) {
#pragma omp ordered
MPI_Igather(M1.data()+j, 1, MPI_DOUBLE,
M2.data(), 1, MPI_DOUBLE, j,
MPI_COMM_WORLD, &requests[j]);
}
但这会带走您希望从线程中获得的大部分加速。
MPI 标准在 §6.12 中指出
All processes must call collective operations (blocking and nonblocking) in the same order per communicator.
两个具有不同根参数的Igather操作是不同的集合操作。您的线程在同一个通信器上没有任何同步地发出它们。所有进程的结果顺序可能不同。
一个解决方案是为每个线程使用不同的通信器(如果所有进程使用相同数量的线程)。这样您就可以在每个通信器上进行单一的集体操作,并且可以确保正确的线程在彼此之间交换数据。如果您多次重复该代码,只需预先创建它们并重新使用它们。
我正在尝试启动一系列 MPI_Igather 调用(来自 MPI 4 的非阻塞集体),做一些工作,然后每当 Igather 完成时,对该数据做更多的工作。
工作正常,除非我在每个 MPI 等级上从不同的线程 启动 Igather。 在那种情况下,即使我调用 MPI_Init_thread 以确保提供了 MPI_THREAD_MULTIPLE,我也经常遇到死锁。 非阻塞集合没有匹配发送和接收的标签,但我认为这是由 MPI_Request 对象处理的 与每个集体行动相关联?
我发现失败的最简单示例是:
- 给定 np 个 MPI 等级,每个等级都有一个长度为 np 的局部数组
- 为每个元素 i 启动一个 MPI_Igather,在进程 i 上收集这些元素。
- i-loop 使用 OpenMP 并行化
- 然后调用MPI_Waitall()完成所有通信。这是将 OMP_NUM_THREADS 设置为大于 1 的值时程序挂起的地方。
我做了这个程序的两个变体:igather_threaded.cpp(下面的代码),其行为如上所述,以及 igather_threaded_v2.cpp,它收集 MPI 等级 0 上的所有内容。这个版本不会死锁, 但数据也没有正确排序。
igather_threaded.cpp:
#include <iostream>
#include <memory>
#include <mpi.h>
#define PRINT_ARRAY(STR,PTR) \
for (int r=0; r<nproc; r++){\
if (rank==r){\
for (int j=0; j<nproc; j++){\
(STR) << (PTR)[j] << " ";\
}\
(STR) << std::endl << std::flush;\
}\
MPI_Barrier(MPI_COMM_WORLD);\
}
int main(int argc, char* argv[]) {
int provided;
MPI_Init_thread(&argc,&argv, MPI_THREAD_MULTIPLE, &provided);
if (provided!=MPI_THREAD_MULTIPLE){
std::cerr << "Your MPI installation does not provide threading level MPI_THREAD_MULTIPLE, required for this program." << std::endl;
MPI_Abort(MPI_COMM_WORLD, -1);
}
int rank, nproc;
MPI_Comm_rank(MPI_COMM_WORLD,&rank);
MPI_Comm_size(MPI_COMM_WORLD,&nproc);
std::unique_ptr<double[]> M1(new double[nproc]);
std::unique_ptr<double[]> M2(new double[nproc]);
for (int j=0; j<nproc; j++) M1[j] = rank*nproc+j+1;
PRINT_ARRAY(std::cout,M1);
std::unique_ptr<MPI_Request[]> requests(new MPI_Request[nproc]);
#pragma omp parallel for schedule(static) shared(requests)
for (int j=0; j<nproc; j++){
MPI_Igather(
M1.get()+j, 1, MPI_DOUBLE,
M2.get(), 1, MPI_DOUBLE, j,
MPI_COMM_WORLD, &requests[j]
);
}
MPI_Waitall(nproc,requests.get(),MPI_STATUSES_IGNORE);
if (rank==0) std::cout << " => "<<std::endl;
PRINT_ARRAY(std::cout,M2);
MPI_Finalize();
return 0;
}
我的问题是:我的代码在理论上是否正确,这是 OpenMPI(此处使用 4.0.3)中的错误,还是我遗漏了任何不允许启动 MPI_Igather 调用的内容 多线程?我也用 Intel MPI 尝试过,结果相似。
为了编译,我使用 OpenMPI 4.0.3(配置为支持 MPI_THREAD_MULTIPLE)、gcc 11.2.0 和命令行
> mpicxx -std=c++17 -fopenmp -o igather_threaded igather_threaded.cpp
到运行,我用
mpirun -np 4 --bind-to none env OMP_NUM_THREADS=4 env OMP_PROC_BIND=false ./igather_threaded
您可能需要增加 MPI 等级数 and/or OpenMP 线程,and/or 运行 多次,因为确切的行为是不确定的。
在你的方案中,因为集合体是在不同的线程中启动的,所以它们可以按任意顺序启动。拥有不同的请求不足以消除它们的歧义:MPI 坚持它们在每个进程上以相同的顺序启动。
您可以通过以下方式解决此问题:
#pragma omp parallel for schedule(static) shared(requests) ordered
for (int j=0; j<nprocs; j++) {
#pragma omp ordered
MPI_Igather(M1.data()+j, 1, MPI_DOUBLE,
M2.data(), 1, MPI_DOUBLE, j,
MPI_COMM_WORLD, &requests[j]);
}
但这会带走您希望从线程中获得的大部分加速。
MPI 标准在 §6.12 中指出
All processes must call collective operations (blocking and nonblocking) in the same order per communicator.
两个具有不同根参数的Igather操作是不同的集合操作。您的线程在同一个通信器上没有任何同步地发出它们。所有进程的结果顺序可能不同。
一个解决方案是为每个线程使用不同的通信器(如果所有进程使用相同数量的线程)。这样您就可以在每个通信器上进行单一的集体操作,并且可以确保正确的线程在彼此之间交换数据。如果您多次重复该代码,只需预先创建它们并重新使用它们。