MPI动态分配任务

时间:2019-07-09 13:28:18

标签: c++ mpi

我有一个在Windows HPC群集(12个节点,每个节点24个核心)上运行的C ++ MPI程序。

  • 程序的逻辑非常简单:
    1. 有很多任务
    2. 开始时,程序将任务平均分配给每个MPI进程
    3. 每个MPI流程都执行其任务
    4. 完成所有操作后,使用MPI reduce将结果收集到根进程中。

有一个问题。每个任务的执行时间可能截然不同,因此我无法提前告知。平均分配任务将导致许多进程处于空闲状态。这会浪费大量计算机资源,并使总执行时间更长。

我正在考虑一种可行的解决方案。

  • 过程是这样的。
    1. 任务池分为多个小包(例如一个小包10个任务)
    2. 每个MPI进程在空闲时(尚未收到包裹或未完成前一个包裹)都提取一个包裹
    3. 继续执行步骤2,直到任务池用完
    4. 使用MPI Reduce将所有结果收集到根进程中

据我了解,此方案需要跨节点/进程的通用计数器(以避免不同的MPI进程执行同一宗地),并且对其进行更改需要一些锁定/同步机制。它当然有开销,但是通过适当的调整,我认为它可以帮助提高性能。

我对MPI不太熟悉,并且有一些实现问题。我可以想到两种方法来实现此通用计数器

  1. 使用MPI I / O技术,将此计数器写入文件中,当打包时,增加此计数器(肯定需要文件锁定机制)
  2. 使用MPI一侧通讯/共享内存。将此计数器放在共享内存中,并在提取包裹时增加它。 (当然需要同步机制)

不幸的是,我对这两种技术都不熟悉,并且想探究上述两种方法的可能性,实现或可能的缺点。样例代码将不胜感激。

如果您还有其他解决问题或建议的方法,那也很好。谢谢。

后续行动:

感谢所有有用的建议。我按照使用进程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;
}

它不起作用,并且永不停止。关于如何使其工作的任何建议?这段代码是否反映了这个想法,还是我错过了什么?谢谢

1 个答案:

答案 0 :(得分:1)

[将我的评论转换为答案...]

给定n进程,您可以让您的第一个进程p0为其他n - 1进程分派任务。首先,它将与其他n - 1进程进行点对点通信,以便每个人都有工作要做,然后它将阻塞在Recv上。当任何给定过程完成时,例如说p3,它将把结果发送回p0。此时,p0将通过以下两种方式之一向p3发送另一条消息:

1)另一个任务

2)如果没有剩余任务,则发出某种终止信号。 (使用消息的“标签”是一种简单的方法。)

很显然,p0会循环遍历该逻辑,直到没有剩下的任务为止,在这种情况下,它也会调用MPI_Finalize

与您在评论中的想法不同,这不是轮循。它首先将工作分配给每个流程或工人,然后每完成一个工作就给另一工作...