如何使用MPI传输带有动态数组的自定义结构体?

3

有一个简单的示例来描述我的问题。我有一个包含动态数组的自定义结构体。

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

根进程(进程0)有一个这样的结构体数组 nums[4]

我想通过 MPI_Scatter 将数组的块发送给不同的进程(例如,2个进程)。主要问题是我希望数组 d[] 是动态的。

主要代码如下:

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;
}

如果我将结构体 my_data_type 的定义中的 int d[]; 改为 int d[2];,那么我肯定会得到期望的结果。
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

如您所见,我知道问题与动态数组有关,但在我的项目中我不能使用静态数组。那么我应该如何修改上述代码以获得预期结果?


1
如果您使用 d[],那么 sizeof(...) 的结果将不是您所期望的,这会导致短小的 malloc 和数据损坏。 - Gilles Gouaillardet
亲爱的朋友,谢谢你的评论。我发现了一个bug并已经更新了它(分配结构体数组nums[4]sub_nums[4]的内存)。但问题仍然存在。而且我无法分配动态数组d[],因为数组类型'int []'不可分配。 - Joxixi
1
我认为你无法以合理的方式使用具有灵活数组成员的结构体数组。 - ptb
我同意你的观点。实际上,我想知道如何以另一种方式处理具有灵活数组成员的结构体数组。 - Joxixi
我更新了我的答案,告诉你如何使用FAMs处理结构体数组。 - ptb
2个回答

2

您的根本问题不是mpi,而是使用具有灵活数组成员的结构数组。以下是一个示例程序,以说明这个问题。

#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);

感谢您的帮助!您是正确的,我可以将其适应到我的问题中。 - Joxixi

1
我可能会错,但我认为您想保存指向数组的指针(即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;
}

希望它有所帮助。

1
谢谢您的建议。但问题在于,将指针而不是数组发送到其他MPI节点似乎是没有意义的。 - Joxixi
请查看此问题以了解如何通过MPI通过指针发送数据。看起来您确实能够做到。我直到现在才听说过MPI,所以我很好奇这是否可能。 https://stackoverflow.com/questions/32217553/how-to-send-pointer-in-struct-in-mpi - jcrizk
另外,在 C 中,当数组用作函数输入时,它们无论如何都会衰变为指针,因此对我来说,将数组放入函数中能够工作,而将指针放入函数中却不能工作是没有意义的。我认为由于衰变,该函数无论如何都将使用指针。 - jcrizk
我发现指针确实起作用,但是在自定义结构体内部使用指针时出了些问题。事实上,我的观点就像Jeff的评论所说:“你不能发送指针,因为它在分配它的进程之外是没有意义的。”一个进程的内存地址对其他进程(在其他计算机上)没有用处。 - Joxixi
无论如何,我将尝试仅使用指针,而不是在结构体中使用。那样可能会起作用。 - Joxixi
也许这会澄清我的意思。我的意思是,你应该发送 *( instance->PointerToAnArray) 而不是 instance->PointerToAnArray,因为正如你所指出的,发送 instance->PointerToAnArray 对于你要发送到的人可能是没有意义的。 - jcrizk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接