无法理解MPI中的MPI_Reduce_scatter

Sto*_*eak 9 c arrays mpi multidimensional-array mpich

我试图理解这个MPI_Reduce_scatter功能,但似乎我的推论总是错误的:(
文档说(链接):

MPI_Reduce_scatter首先对count = S(i)的向量进行逐元素减少,在sendbuf,count和datatype定义的发送缓冲区中recvcounts [i]元素.接下来,结果的结果向量被分成n个不相交的段,其中n是组中的进程数.段i包含recvcounts [i]元素.第i个段被发送到进程i并存储在由recvbuf,recvcounts [i]和datatype定义的接收缓冲区中.

我有以下(非常简单)的C程序,我希望获得第一个recvcounts [i]元素的最大值,但似乎我做错了...

#include <stdio.h>
#include <stdlib.h>
#include "mpi.h"

#define NUM_PE 5
#define NUM_ELEM 3

char *print(int arr[], int n);

int main(int argc, char *argv[]) {
    int rank, size, i, n;
    int sendbuf[5][3] = {
        {  1,  2,  3 },
        {  4,  5,  6 },
        {  7,  8,  9 },
        { 10, 11, 12 },
        { 13, 14, 15 }
    };
    int recvbuf[15] = {0};
    int recvcounts[5] = {
        3, 3, 3, 3, 3
    };

    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &size);

    n = sizeof(sendbuf[rank]) / sizeof(int);
    printf("sendbuf (thread %d): %s\n", rank, print(sendbuf[rank], n));

    MPI_Reduce_scatter(sendbuf, recvbuf, recvcounts, MPI_INT, MPI_MAX, MPI_COMM_WORLD);

    n = sizeof(recvbuf) / sizeof(int);
    printf("recvbuf (thread %d): %s\n", rank, print(recvbuf, n)); // <--- I receive the same output as with sendbuf :(

    MPI_Finalize();

    return 0;
}

char *print(int arr[], int n) { } // it returns a string formatted as the following output
Run Code Online (Sandbox Code Playgroud)

我的程序输出与recvbuf和sendbuf相同.我希望recvbuf包含最大值:

$ mpicc 03_reduce_scatter.c
$ mpirun -n 5 ./a.out
sendbuf (thread 4): [ 13, 14, 15 ]
sendbuf (thread 3): [ 10, 11, 12 ]
sendbuf (thread 2): [  7,  8,  9 ]
sendbuf (thread 0): [  1,  2,  3 ]
sendbuf (thread 1): [  4,  5,  6 ]
recvbuf (thread 1): [  4,  5,  6,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 ]
recvbuf (thread 2): [  7,  8,  9,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 ]
recvbuf (thread 0): [  1,  2,  3,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 ]
recvbuf (thread 3): [ 10, 11, 12,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 ]
recvbuf (thread 4): [ 13, 14, 15,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 ]
Run Code Online (Sandbox Code Playgroud)

谢谢!

Jon*_*rsi 21

是的,Reduce_scatter的文档很简洁,并没有被广泛使用,所以没有很多例子.这个OCW麻省理工学院讲座的前几张幻灯片有一个很好的图表,并建议一个用例.

通常情况下,关键是阅读MPI文档并特别注意对实施者的建议:

"MPI_REDUCE_SCATTER例程在功能上等同于:MPI_REDUCE集合操作,其计数等于recvcounts [i]的总和,后跟MPI_SCATTERV,sendcounts等于recvcounts."

那么让我们来看看你的例子:这条线,

MPI_Reduce_scatter(sendbuf, recvbuf, recvcounts, MPI_INT, MPI_MAX, MPI_COMM_WORLD);
Run Code Online (Sandbox Code Playgroud)

将相当于:

int totcounts = 15;  // = sum of {3, 3, 3, 3, 3}
MPI_Reduce({1,2,3...15}, tmpbuffer, totcounts, MPI_INT, MPI_MAX, 0, MPI_COMM_WORLD);
MPI_Scatterv(tmpbuffer, recvcounts, [displacements corresponding to recvcounts], 
              MPI_INT, rcvbuffer, 3, MPI_INT, 0, MPI_COMM_WORLD);
Run Code Online (Sandbox Code Playgroud)

所以每个人都会发送相同的数字{1 ... 15},并且每列都会相互最大化,导致{max(1,1 ... 1),max( 2,2 ... 2)... max(15,15 ... 15)} = {1,2,... 15}.

然后这些将被分散到处理器,一次3个,导致{1,2,3},{4,5,6},{7,8,9} ......

所以,这就是确实发生,我们如何得到你有什么发生的情况发生?我知道你希望每一行得到最大化,并且每个处理器得到"它"对应的行 - 最大值.例如,假设数据如下所示:

Proc 0: 1 5 9 13
Proc 1: 2 6 10 14
Proc 2: 3 7 11 15
Proc 3: 4 8 12 16
Run Code Online (Sandbox Code Playgroud)

并且我们希望结束Proc 0(比如说)具有所有第0个数据的最大值,proc 1具有所有第1个的最大值,等等,所以我们最终得到

Proc 0: 4
Proc 1: 8
Proc 2: 12
Proc 3: 16
Run Code Online (Sandbox Code Playgroud)

那么让我们看看如何做到这一点.首先,每个人都将拥有一个值,因此所有的计数都是1.其次,每个流程都必须发送单独的数据.所以我们会看到这样的东西:

#include <stdio.h>
#include <stdlib.h>
#include "mpi.h"

int main(int argc, char *argv[]) {
    int rank, size, i, n;

    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &size);

    int sendbuf[size];
    int recvbuf;

    for (int i=0; i<size; i++)
        sendbuf[i] = 1 + rank + size*i;

    printf("Proc %d: ", rank);
    for (int i=0; i<size; i++) printf("%d ", sendbuf[i]);
    printf("\n");

    int recvcounts[size];
    for (int i=0; i<size; i++)
        recvcounts[i] = 1;

    MPI_Reduce_scatter(sendbuf, &recvbuf, recvcounts, MPI_INT, MPI_MAX, MPI_COMM_WORLD);

    printf("Proc %d: %d\n", rank, recvbuf);

    MPI_Finalize();

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

运行给出(输出重新排序以便清晰):

Proc 0: 1 5 9 13 
Proc 1: 2 6 10 14 
Proc 2: 3 7 11 15
Proc 3: 4 8 12 16

Proc 0: 4
Proc 1: 8
Proc 2: 12
Proc 3: 16
Run Code Online (Sandbox Code Playgroud)

  • 哇!很少见到如此具体的问题的详细和信息丰富的答案.非常感谢,感谢您抽出宝贵时间撰写详尽的解释! (2认同)