Как использовать MPI для передачи пользовательской структуры с массивом Dynami c. - PullRequest
2 голосов
/ 28 января 2020

Есть простой пример для описания моего вопроса. У меня есть пользовательская структура, которая содержит динамический c массив

struct my_data_type {
    int c;
    int d[];
};

, а процесс root (процесс 0) имеет массив такой структуры nums[4].

Я хочу отправлять куски массива различным процессам (например, 2 процессам) через MPI_Scatter. Основная проблема здесь в том, что я хочу, чтобы этот массив d[] был динамическим c.

Основной код следующий:

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

    MPI_Init(NULL, NULL);

    int my_size; MPI_Comm_size(MPI_COMM_WORLD, &my_size);
    int my_rank; MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);

    int len = 2; //example: the dynamic array d contains len=2 elements
    my_data_type *nums //nums[4]
        = (my_data_type*)malloc((sizeof(my_data_type) + sizeof(int) * len) * 4);
    my_data_type *sub_nums //sub_nums[2]
        = (my_data_type*)malloc((sizeof(my_data_type) + sizeof(int) * len) * 2);

    if (my_rank == 0) { //just some examples
        nums[0].c = 0; nums[1].c = 1; nums[2].c = 2; nums[3].c = 3;
        nums[0].d[0] = 10; nums[1].d[0] = 11; nums[2].d[0] = 12; nums[3].d[0] = 13;
        nums[0].d[1] = 14; nums[1].d[1] = 15; nums[2].d[1] = 16; nums[3].d[1] = 17;
    }

    MPI_Datatype mpi_data_type; //new datatype
    int blocklens[2];
    MPI_Datatype old_types[2];
    MPI_Aint indices[2];

    blocklens[0] = 1; blocklens[1] = len;
    old_types[0] = MPI_INT; old_types[1] = MPI_INT;
    MPI_Address(&nums[0].c, &indices[0]);
    MPI_Address(&nums[0].d[0], &indices[1]);
    indices[1] = indices[1] - indices[0];
    indices[0] = 0;

    MPI_Type_create_struct(2, blocklens, indices, old_types, &mpi_data_type);
    MPI_Type_commit(&mpi_data_type);

    MPI_Scatter(nums, 2, mpi_data_type,
                sub_nums, 2, mpi_data_type,
                0, MPI_COMM_WORLD);

    cout << "rank " << my_rank << ": " << endl;
    cout << "c: " << sub_nums[0].c << ", " << sub_nums[1].c << endl;
    cout << "d: " << sub_nums[0].d[0] << ", " << sub_nums[0].d[1] << ", ";
    cout << sub_nums[1].d[0] << ", " << sub_nums[1].d[1] << endl;

    MPI_Finalize();

    return 0;
}

Если я изменю int d[]; на int d[2]; в определении struct my_data_type, я, конечно, получу ожидаемые результаты, такие как

rank 0: 
c: 0, 1
d: 10, 14, 11, 15
rank 1: 
c: 2, 3
d: 12, 16, 13, 17

Но если нет, то результаты следующие:

rank 0: 
c: 0, 10
d: 10, 14, 14, 15
rank 1: 
c: 33, 0
d: 0, 0, 0, 1

Как вы видите, я знаю, что проблема в массиве Dynami c, но я не могу использовать STATI c в моем проекте. Итак, как я могу изменить свой код выше, чтобы получить ожидаемые результаты?

Ответы [ 2 ]

2 голосов
/ 28 января 2020

Ваша фундаментальная проблема не в mpi, а в использовании массивов struct с гибкими элементами массива. Вот пример программы, чтобы проиллюстрировать проблему

#include <assert.h>
#include <stdlib.h>
#include <stdint.h>

typedef struct s s;
struct s
{
    int c;
    int d[];
};

int main(int argc, char* argv[])
{
    assert(sizeof(s) == sizeof(int));

    int len = 4;
    s* okay = malloc(sizeof(*okay) + sizeof(int)*len);

    intptr_t true_size = (intptr_t)&okay->d[len] -(intptr_t)(okay);
    assert(true_size == ((len+1)*sizeof(int)));

    int nbad = 6;
    s* bad = malloc((sizeof(*bad) + sizeof(int)*len)*nbad);


    intptr_t bad_size = (intptr_t)&bad[1] -(intptr_t)&bad[0];

    /* this size mismatch means arrays of `s` do not do what you think they do */
    assert(bad_size != true_size);
    assert(bad_size == sizeof(int));

    assert((char*)&bad[1] == (char*)&bad[0].d[0]);
    assert((char*)&bad[2] == (char*)&bad[0].d[1]);
    assert((char*)&bad[3] == (char*)&bad[0].d[2]);

    assert((char*)&bad[1].d[0] == (char*)&bad[0].d[1]);
    assert((char*)&bad[2].d[0] == (char*)&bad[0].d[2]);
    assert((char*)&bad[3].d[0] == (char*)&bad[0].d[3]);
}

Для обработки массивов структур с гибкими элементами массива вам нужно будет вручную вычислить смещения памяти для индексации, а не полагаться на компилятор. Таким образом, вы можете определить вспомогательную функцию следующим образом:

s* s_index(const s* a, int len, int index)
{
    uintptr_t true_size = sizeof(*a) + len*sizeof(int);
    return (s*)((char*)a + index * true_size);
}

И затем использовать s_index для доступа к нужному члену массива вместо конструкций bad[0], bad[1]:

s* first = s_index(bad, len, 0);
s* second = s_index(bad, len, 1);
assert((char*)&first->d[len] == (char *)second);
1 голос
/ 28 января 2020

Я могу ошибаться в этом, но я думаю, что вы хотите сохранить указатель на массив (т.е. int** myArrPointer), потому что я думаю, что вам нужно сделать, так как я не думаю, что вы можете выделить массив в C (то есть myArr = myOtherArr), чтобы:

  1. Рассчитать размер нового массива
  2. Выделить память для этого нового массива
  3. Сохраните указатель на этот новый массив в вашей структуре

Вероятно, ваша структура должна будет выглядеть примерно так:

struct my_data_type 
{
    int ArrSize;
    int** PointerToAnArray;
};

void SomeFunForSwappingArrays(my_data_type* instance, int newArrSize)
{
    int* newArr = (int*)malloc(newArrSize*sizeof(int));
    //free the memory of the old array. if you don't need the data anymore, i would
    //consider doing this.
    free(*(instance->PointerToAnArray));
    //save the memory address of the new array
    instance->PointerToAnArray = &newArr;
    instance->ArrSize = newArrSize;
}

Надеюсь, это поможет.

...