Что такое аргумент displs в MPI Scatterv?


Аргумент displs из функции MPI_Scatterv() называется "целочисленным массивом (размером группы длины). Запись i задает смещение (относительно sendbuf, из которого следует принимать исходящие данные для обработки i". Предположим, что у меня есть sendcounts аргумент

    int sendcounts[7] = {3,3,3,3,4,4,4};

Я рассуждаю так, что массив displs всегда должен начинаться со значения 0, так как смещение первой записи равно 0 относительно sendbuf, поэтому в моем примере выше displs должен выглядеть например:

    int displs[7] = {0,3,6,9,13,17,21};

Верно ли это? Я знаю, что это тривиальный вопрос, но по какой-то причине сеть не помогает вообще. Там нет хороших примеров, поэтому мой вопрос.

2 3

2 ответа:

Да, смещения дают корневой информации информацию о том, какие элементы для отправки в конкретную задачу-смещение исходного элемента. Поэтому в большинстве простых случаев (например, вы используете MPI_Scatter, но подсчеты не делятся равномерно) это может быть немедленно вычислено из информации о подсчетах:

displs[0] = 0;              // offsets into the global array
for (size_t i=1; i<comsize; i++)
    displs[i] = displs[i-1] + counts[i-1];
Но это не обязательно должно быть так; единственное ограничение заключается в том, что данные, которые вы посылаете, не могут перекрываться. Вы также можете считать со спины:
displs[0] = globalsize - counts[0];                 
for (size_t i=1; i<comsize; i++)
    displs[i] = displs[i-1] - counts[i];

Или любой произвольный приказ будет работать, как хорошо.

И вообще вычисления могут быть более сложными, потому что типы буфера отправки и буфера приема должны бытьсогласованными , но не обязательно одинаковыми - вы часто получаете это, например, при отправке многомерных срезов массива.

В качестве примера простых случаев ниже приведены прямые и обратные случаи:

#include <iostream>
#include <vector>
#include "mpi.h"

int main(int argc, char **argv) {
    const int root = 0;             // the processor with the initial global data

    size_t globalsize;
    std::vector<char> global;       // only root has this

    const size_t localsize = 2;     // most ranks will have 2 items; one will have localsize+1
    char local[localsize+2];        // everyone has this
    int  mynum;                     // how many items 

    MPI_Init(&argc, &argv); 

    int comrank, comsize;
    MPI_Comm_rank(MPI_COMM_WORLD, &comrank);
    MPI_Comm_size(MPI_COMM_WORLD, &comsize);

    // initialize global vector
    if (comrank == root) {
        globalsize = comsize*localsize + 1;
        for (size_t i=0; i<globalsize; i++) 
            global.push_back('a'+i);
    }

    // initialize local
    for (size_t i=0; i<localsize+1; i++) 
        local[i] = '-';
    local[localsize+1] = '\0';

    int counts[comsize];        // how many pieces of data everyone has
    for (size_t i=0; i<comsize; i++)
        counts[i] = localsize;
    counts[comsize-1]++;

    mynum = counts[comrank];
    int displs[comsize];

    if (comrank == 0) 
        std::cout << "In forward order" << std::endl;

    displs[0] = 0;              // offsets into the global array
    for (size_t i=1; i<comsize; i++)
        displs[i] = displs[i-1] + counts[i-1];

    MPI_Scatterv(global.data(), counts, displs, MPI_CHAR, // For root: proc i gets counts[i] MPI_CHARAs from displs[i] 
                 local, mynum, MPI_CHAR,                  // I'm receiving mynum MPI_CHARs into local */
                 root, MPI_COMM_WORLD);                   // Task (root, MPI_COMM_WORLD) is the root

    local[mynum] = '\0';
    std::cout << comrank << " " << local << std::endl;

    std::cout.flush();
    if (comrank == 0) 
        std::cout << "In reverse order" << std::endl;

    displs[0] = globalsize - counts[0];                 
    for (size_t i=1; i<comsize; i++)
        displs[i] = displs[i-1] - counts[i];

    MPI_Scatterv(global.data(), counts, displs, MPI_CHAR, // For root: proc i gets counts[i] MPI_CHARAs from displs[i] 
                 local, mynum, MPI_CHAR,                  // I'm receiving mynum MPI_CHARs into local */
                 root, MPI_COMM_WORLD);                   // Task (root, MPI_COMM_WORLD) is the root

    local[mynum] = '\0';
    std::cout << comrank << " " << local << std::endl;

    MPI_Finalize();
}

Бег дает:

In forward order
0 ab
1 cd
2 ef
3 ghi

In reverse order
0 hi
1 fg
2 de
3 abc

Да, ваше рассуждение верно-длясмежных данных. Смысл параметра displacements в MPI_Scatterv также состоит в том, чтобы разрешить шаговые данные, что означает наличие неиспользуемых пробелов памяти в sendbuf между блоками.

Вот примердля смежных данных . Официальная документация фактически содержит хорошие примерыдля пошаговых данных .