正确的方法来创建鬼区MPI [halos]

时间:2016-04-30 21:28:01

标签: c parallel-processing mpi

晚安

我参加并行编程课程。老师给了我们一个涉及用于模板计算的域分区的任务。对于这种类型的计算(有限差分),并行化代码的最常用方法是对域进行分区并创建一些重影区域(晕圈)。

为了更好地理解MPI中鬼区的创建,我编写了这个简单的例子来初始化一些内部值= 123和边界值88的数组。在所有通信结束时,所有重影值应保持为8.在一个节点中我#39;得到123个值。

连续剧(没有幽灵):

FINAL = [:b, :b, :b, :e, :r, :r, :r]
SIZE = FINAL.size
solve [:r, :r, :r, :e, :b, :b, :b]
  #=> [:red_slide, :blue_jump, :blue_slide, :red_jump, :red_jump, :red_slide,
  #    :blue_jump, :blue_jump, :blue_jump, :red_slide, :red_jump, :red_jump,
  #    :blue_slide, :blue_jump, :red_slide] 

两个分区:

   123 - 123 - ... - 123 - 123

三个分区:

   123 - 123 - ... - 88  |||  88 - ... - 123 - 123

除了这个bug之外,这里的主要问题是关于创建和维护更新鬼区的正确方法。除了我的凌乱if(myid == .... else if(myid = ... else类型的实现?人们通常如何实现这种并行性?

)之外,还有更清洁的解决方案吗?
   123 - 123 - ... - 88  |||  88 - ... - 123 - 123 - 88 ||| 88 - ... - 123 - 123
谢谢你们!

1 个答案:

答案 0 :(得分:7)

使用笛卡尔虚拟拓扑和发送 - 接收操作,可以在MPI中优雅地实现Halos。

首先,在条件运算符中有许多与秩相关的逻辑会使代码难以阅读和理解。当代码是对称的时,即当所有等级执行相同的代码时,它会更好。可以使用MPI_PROC_NULL空等级来处理转角情况 - 发送到该等级或从该等级接收会导致无操作。因此,这足以做到:

// Compute the rank of the left neighbour
leftProc = myid - 1;
if (leftProc < 0) leftProc = MPI_PROC_NULL;
// Compute the rank of the right neighbour
rightProc = myid + 1;
if (rightProc >= numProc) rightProc = MPI_PROC_NULL;

// Halo exchange in forward direction
MPI_Sendrecv(&myArray[localSize], 1, MPI_INT, rightProc, 0,   // send last element to the right
             &myArray[0], 1, MPI_INT, leftProc, 0,            // receive into left halo
             MPI_COMM_WORLD);
// Halo exchange in reverse direction
MPI_Sendrecv(&myArray[1], 1, MPI_INT, leftProc, 0,            // send first element to the left
             &myArray[localSize+1], 1, MPI_INT, rightProc, 0, // receive into right halo
             MPI_COMM_WORLD);

该代码适用于任何等级,即使是两端的代码 - 源或目标都是空等级,并且在相应的方向上不会发生实际传输。它也适用于任意数量的MPI流程,从一个到多个。它要求所有队伍都有双方的光环,包括那些并不真正需要它的人(两个角落等级)。人们可以在那些虚拟光环中存储有用的东西,例如边界值(例如,在解决偏微分方程时)或者仅仅存储废物,这通常可以忽略不计。

在您的代码中,您使用了错误的非阻塞操作。这些都很棘手,需要小心。可以而且应该使用MPI_Sendrecv代替。它同时执行发送和接收操作,从而防止死锁(只要每次发送都有匹配的接收)。

如果域是周期性的,则秩计算逻辑变为简单:

// Compute the rank of the left neighbour
leftProc = (myid - 1 + numProc) % numProc;
// Compute the rank of the right neighbour
rightProc = (myid + 1) % numProc;

可以创建笛卡尔虚拟拓扑,然后使用MPI_Cart_shift来查找两个邻居的等级,而不是进行算术运算:

// Create a non-periodic 1-D Cartesian topology
int dims[1] = { numProc };
int periods[1] = { 0 };   // 0 - non-periodic, 1 - periodic
MPI_Comm cart_comm;
MPI_Cart_create(MPI_COMM_WORLD, 1, dims, periods, 1, &cart_comm);

// Find the two neighbours
MPI_Cart_shift(cart_comm, 0, 1, &leftProc, &rightProc);

光环交换的代码保持不变,cart_comm应该替换MPI_COMM_WORLD的唯一区别。 MPI_Cart_shift会自动处理角落情况,并在适当时返回MPI_PROC_NULL。该方法的优点是,只需翻转periods[]数组中的值,即可轻松在非周期性域和周期性域之间切换。

halos必须经常更新,这取决于算法。对于大多数迭代方案,更新必须在每次迭代开始时进行。可以通过引入多级光晕并使用外层中的值来计算内部值来降低通信频率。

总而言之,您的main函数可以简化为(不使用笛卡尔拓扑):

int main(int argc, char *argv[]){

    int i;
    int localSize;
    int numProc;
    int myid;

    int leftProc;
    int rightProc;

    int * myArray;
    int fullDomainSize = 16;

    MPI_Init(&argc, &argv);
    MPI_Comm_size(MPI_COMM_WORLD, &numProc);
    MPI_Comm_rank(MPI_COMM_WORLD, &myid);

    // Compute neighbouring ranks
    rightProc = myid + 1;
    if (rightProc >= numProc) rightProc = MPI_PROC_NULL;
    leftProc = myid - 1;
    if (leftProc < 0) leftProc = MPI_PROC_NULL;

    // Lets get each partition size.
    localSize = WhichSize(myid, numProc, fullDomainSize);

    // Allocate arrays.
    myArray = (int*)malloc((localSize+ 2)*sizeof(int));

    // Now we will fill the arrays with a dummy value 123. For the
    // boundaries (ghosts) we will fill than with 80. Just to differe
    // ntiate.

    //printf("--------------------------------------------------\n");
    //printf("Filling node arrays usage with values... \n");

    for (i = 1; i<localSize; i++){
        myArray[i] = 123;
    }

    // ghosts.
    myArray[localSize+1] = 8;
    myArray[0] = 8;

    //printf("-------------------------------\n");
    //printf("Communicating Boundary ghosts !\n");
    //printf("-------------------------------\n");

    //printf("Sending ghost value to the right\n");
    MPI_Sendrecv(&myArray[localSize], 1, MPI_INT, rightProc, 12345,
                 &myArray[0], 1, MPI_INT, leftProc, 12345,
                 MPI_COMM_WORLD);

    //printf("Sending ghost value to the left\n");
    MPI_Sendrecv(&myArray[1], 1, MPI_INT, leftProc, 12345,
                 &myArray[localSize+1], 1, MPI_INT, rightProc, 12345,
                 MPI_COMM_WORLD);

    // Now I Want to see if the ghosts are in place !.

    printf("[%d] The head ghost is: %d\n", myid, myArray[0]);
    printf("[%d] The tail ghost is: %d\n", myid, myArray[localSize + 1]);

    MPI_Finalize();

    return 0;
}