MPI 动态分配任务
MPI dynamically allocate tasks
我有一个在 Windows HPC 集群(12 个节点,每个节点 24 个内核)上运行的 C++ MPI 程序。
- 程序的逻辑非常简单:
- 有一个任务池
- 开始时,程序将任务平均分配给每个 MPI 进程
- 每个 MPI 进程执行它们的任务
- 一切都完成后,使用 MPI reduce 将结果收集到根进程。
有一个问题。每个任务的执行时间都可能截然不同,我无法提前告知。平均分配任务将导致大量进程空闲等待。这会浪费大量的计算机资源并使总执行时间变长。
我正在考虑一种可能有效的解决方案。
- 过程是这样的。
- 任务池被分成小包(比如10个任务一个包)
- 每个MPI进程在空闲的时候(还没有收到包裹,或者上一个包裹收完了)每次取一个包裹
- 继续第2步,直到任务池耗尽
- 使用 MPI reduce 将所有结果收集到根进程
据我了解,这个方案需要一个跨 nodes/process 的通用计数器(以避免不同的 MPI 进程执行同一个包裹)并且改变它需要一些 lock/sync 机制。它当然有它的开销,但通过适当的调整,我认为它可以帮助提高性能。
我对 MPI 不太熟悉并且有一些实现问题。我可以想到两种方法来实现这个通用计数器
- 使用MPI I/O技术,将这个计数器写在文件中,当一个包裹被拿走时,增加这个计数器(肯定需要文件锁定机制)
- 一侧使用 MPI communication/shared 内存。将这个计数器放在共享内存中,并在取包裹时增加它。 (肯定需要同步机制)
不幸的是,我对这两种技术都不熟悉,想探索上述两种方法的可能性、实现或可能的缺点。示例代码将不胜感激。
如果您有其他解决问题的方法或建议,那也很好。谢谢
跟进:
感谢所有有用的建议。我按照使用进程0作为任务分配器的方案实现了一个测试程序。
#include <iostream>
#include <mpi.h>
using namespace std;
void doTask(int rank, int i){
cout<<rank<<" got task "<<i<<endl;
}
int main ()
{
int numTasks = 5000;
int parcelSize = 100;
int numParcels = (numTasks/parcelSize) + (numTasks%parcelSize==0?0:1);
//cout<<numParcels<<endl;
MPI_Init(NULL, NULL);
int rank, nproc;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &nproc);
MPI_Status status;
MPI_Request request;
int ready = 0;
int i = 0;
int maxParcelNow = 0;
if(rank == 0){
for(i = 0; i <numParcels; i++){
MPI_Recv(&ready, 1, MPI_INT, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
//cout<<i<<"Yes"<<endl;
MPI_Send(&i, 1, MPI_INT, status.MPI_SOURCE, 0, MPI_COMM_WORLD);
//cout<<i<<"No"<<endl;
}
maxParcelNow = i;
cout<<maxParcelNow<<" "<<numParcels<<endl;
}else{
int counter = 0;
while(true){
if(maxParcelNow == numParcels) {
cout<<"Yes exiting"<<endl;
break;
}
//if(maxParcelNow == numParcels - 1) break;
ready = 1;
MPI_Send(&ready, 1, MPI_INT, 0, 0, MPI_COMM_WORLD);
//cout<<rank<<"send"<<endl;
MPI_Recv(&i, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &status);
//cout<<rank<<"recv"<<endl;
doTask(rank, i);
}
}
MPI_Bcast(&maxParcelNow, 1, MPI_INT, 0, MPI_COMM_WORLD);
MPI_Finalize();
return 0;
}
它不起作用,而且永远不会停止。关于如何使其工作的任何建议?这段代码是否反映了正确的想法,还是我遗漏了什么?谢谢
[正在将我的评论转化为答案...]
给定 n
个进程,您可以让第一个进程 p0
为其他 n - 1
个进程分派任务。首先,它将与其他 n - 1
进程进行 point-to-point 通信,以便每个人都有工作要做,然后它将阻塞在 Recv
上。当任何给定进程完成时,比如 p3
,它会将其结果发送回 p0
。此时,p0
将向 p3
发送另一条消息,其中包含以下两种情况之一:
1) 另一个任务
或
2) 如果没有剩余任务,则发出某种终止信号。 (使用消息的 'tag' 是一种简单的方法。)
显然,p0
将循环执行该逻辑,直到没有剩余任务为止,在这种情况下,它也会调用 MPI_Finalize
。
与您在评论中的想法不同,这不是 round-robin。它首先为每个进程或工作人员提供一份工作,然后在完成时返回另一份工作...
我有一个在 Windows HPC 集群(12 个节点,每个节点 24 个内核)上运行的 C++ MPI 程序。
- 程序的逻辑非常简单:
- 有一个任务池
- 开始时,程序将任务平均分配给每个 MPI 进程
- 每个 MPI 进程执行它们的任务
- 一切都完成后,使用 MPI reduce 将结果收集到根进程。
有一个问题。每个任务的执行时间都可能截然不同,我无法提前告知。平均分配任务将导致大量进程空闲等待。这会浪费大量的计算机资源并使总执行时间变长。
我正在考虑一种可能有效的解决方案。
- 过程是这样的。
- 任务池被分成小包(比如10个任务一个包)
- 每个MPI进程在空闲的时候(还没有收到包裹,或者上一个包裹收完了)每次取一个包裹
- 继续第2步,直到任务池耗尽
- 使用 MPI reduce 将所有结果收集到根进程
据我了解,这个方案需要一个跨 nodes/process 的通用计数器(以避免不同的 MPI 进程执行同一个包裹)并且改变它需要一些 lock/sync 机制。它当然有它的开销,但通过适当的调整,我认为它可以帮助提高性能。
我对 MPI 不太熟悉并且有一些实现问题。我可以想到两种方法来实现这个通用计数器
- 使用MPI I/O技术,将这个计数器写在文件中,当一个包裹被拿走时,增加这个计数器(肯定需要文件锁定机制)
- 一侧使用 MPI communication/shared 内存。将这个计数器放在共享内存中,并在取包裹时增加它。 (肯定需要同步机制)
不幸的是,我对这两种技术都不熟悉,想探索上述两种方法的可能性、实现或可能的缺点。示例代码将不胜感激。
如果您有其他解决问题的方法或建议,那也很好。谢谢
跟进:
感谢所有有用的建议。我按照使用进程0作为任务分配器的方案实现了一个测试程序。
#include <iostream>
#include <mpi.h>
using namespace std;
void doTask(int rank, int i){
cout<<rank<<" got task "<<i<<endl;
}
int main ()
{
int numTasks = 5000;
int parcelSize = 100;
int numParcels = (numTasks/parcelSize) + (numTasks%parcelSize==0?0:1);
//cout<<numParcels<<endl;
MPI_Init(NULL, NULL);
int rank, nproc;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &nproc);
MPI_Status status;
MPI_Request request;
int ready = 0;
int i = 0;
int maxParcelNow = 0;
if(rank == 0){
for(i = 0; i <numParcels; i++){
MPI_Recv(&ready, 1, MPI_INT, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
//cout<<i<<"Yes"<<endl;
MPI_Send(&i, 1, MPI_INT, status.MPI_SOURCE, 0, MPI_COMM_WORLD);
//cout<<i<<"No"<<endl;
}
maxParcelNow = i;
cout<<maxParcelNow<<" "<<numParcels<<endl;
}else{
int counter = 0;
while(true){
if(maxParcelNow == numParcels) {
cout<<"Yes exiting"<<endl;
break;
}
//if(maxParcelNow == numParcels - 1) break;
ready = 1;
MPI_Send(&ready, 1, MPI_INT, 0, 0, MPI_COMM_WORLD);
//cout<<rank<<"send"<<endl;
MPI_Recv(&i, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &status);
//cout<<rank<<"recv"<<endl;
doTask(rank, i);
}
}
MPI_Bcast(&maxParcelNow, 1, MPI_INT, 0, MPI_COMM_WORLD);
MPI_Finalize();
return 0;
}
它不起作用,而且永远不会停止。关于如何使其工作的任何建议?这段代码是否反映了正确的想法,还是我遗漏了什么?谢谢
[正在将我的评论转化为答案...]
给定 n
个进程,您可以让第一个进程 p0
为其他 n - 1
个进程分派任务。首先,它将与其他 n - 1
进程进行 point-to-point 通信,以便每个人都有工作要做,然后它将阻塞在 Recv
上。当任何给定进程完成时,比如 p3
,它会将其结果发送回 p0
。此时,p0
将向 p3
发送另一条消息,其中包含以下两种情况之一:
1) 另一个任务
或
2) 如果没有剩余任务,则发出某种终止信号。 (使用消息的 'tag' 是一种简单的方法。)
显然,p0
将循环执行该逻辑,直到没有剩余任务为止,在这种情况下,它也会调用 MPI_Finalize
。
与您在评论中的想法不同,这不是 round-robin。它首先为每个进程或工作人员提供一份工作,然后在完成时返回另一份工作...