【问题标题】:MPI_reduce() with custom Datatype containing dynamically allocated arays : segmentation fault具有包含动态分配数组的自定义数据类型的 MPI_reduce():分段错误
【发布时间】:2012-11-06 06:11:31
【问题描述】:

当我使用包含动态分配数组的自定义 MPI 数据类型时,我不明白为什么 MPI_Reduce() 会出现分段错误。有人知道吗 ?以下代码在 MPI_Reduce() 内使用 2 个处理器崩溃。 但是,如果我删除成员 double *d int MyType 并相应地更改运算符和 MPI 类型例程,则减少完成没有任何问题。

使用动态分配的数组是否存在问题,或者我所做的是否存在根本性错误:

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



typedef struct mytype_s
{
    int c[2];
    double a;
    double b;
    double *d;
} MyType;



void CreateMyTypeMPI(MyType *mt, MPI_Datatype *MyTypeMPI)
{
    int block_lengths[4];                        // # of elt. in each block
    MPI_Aint displacements[4];                   // displac.
    MPI_Datatype typelist[4];                    // list of types
    MPI_Aint start_address, address;            // use for calculating displac.
    MPI_Datatype myType;

    block_lengths[0] = 2;
    block_lengths[1] = 1;
    block_lengths[2] = 1;
    block_lengths[3] = 10;

    typelist[0] = MPI_INT;
    typelist[1] = MPI_DOUBLE;
    typelist[2] = MPI_DOUBLE;
    typelist[3] = MPI_DOUBLE;

    displacements[0] = 0;

    MPI_Address(&mt->c, &start_address);
    MPI_Address(&mt->a, &address);
    displacements[1] = address - start_address;

    MPI_Address(&mt->b,&address);
    displacements[2] = address-start_address;

    MPI_Address(&mt->d, &address);
    displacements[3] = address-start_address;

    MPI_Type_struct(4,block_lengths, displacements,typelist,MyTypeMPI);
    MPI_Type_commit(MyTypeMPI);
}




void MyTypeOp(MyType *in, MyType *out, int *len, MPI_Datatype *typeptr)
{
    int i;
    int j;

    for (i=0; i < *len; i++)
    {
        out[i].a += in[i].a;
        out[i].b += in[i].b;
        out[i].c[0] += in[i].c[0];
        out[i].c[1] += in[i].c[1];

        for (j=0; j<10; j++)
        {
            out[i].d[j] += in[i].d[j];
        }
    }
}




int main(int argc, char **argv)
{
    MyType mt;
    MyType mt2;
    MPI_Datatype MyTypeMPI;
    MPI_Op MyOp;
    int rank;
    int i;

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


    mt.a = 2;
    mt.b = 4;
    mt.c[0] = 6;
    mt.c[1] = 8;
    mt.d = calloc(10,sizeof *mt.d);
    for (i=0; i<10; i++) mt.d[i] = 2.1;

    mt2.a = 0;
    mt2.b = 0;
    mt2.c[0] = mt2.c[1] = 0;
    mt2.d = calloc(10,sizeof *mt2.d);


    CreateMyTypeMPI(&mt, &MyTypeMPI);
    MPI_Op_create((MPI_User_function *) MyTypeOp,1,&MyOp);

    if(rank==0) printf("type and operator are created now\n");

    MPI_Reduce(&mt,&mt2,1,MyTypeMPI,MyOp,0,MPI_COMM_WORLD);

    if(rank==0)
    {




        for (i=0; i<10; i++) printf("%f ",mt2.d[i]);
        printf("\n");
    }

    free(mt.d);
    free(mt2.d);
    MPI_Finalize();

    return 0;
}

【问题讨论】:

  • 哦,我想我明白了:动态分配的内存块与我结构的先前成员不连续,但是 MPI 使用我给他的位移数组并尝试访问 10 MPI_DOUBLE 之后成员'b',这会导致分段错误。那么如何使用动态数组来减少这种结构呢?
  • 你应该让 MyType.d 成为一个数组。或者如果你想要动态内存,使用 memcpy 制作一个包含结构内所有数据并指向的内存包。
  • 是的,我需要动态数组,上面的代码只是一个小例子,在我的真实代码中有很多大数组因此需要动态分配。您能否更具体地说明您对 memcpy 的提议?
  • 不要使用memcpy。 MPI 提供MPI_PackMPI_Unpack 调用来执行可移植数据(解包)到用户提供的内存缓冲区。这个想法基本上是自己构造消息而不是创建数据类型,因为 MPI 根本无法遵循指针引用。

标签: c parallel-processing mpi


【解决方案1】:

让我们看看你的结构:

typedef struct mytype_s
{
    int c[2];
    double a;
    double b;
    double *d;
} MyType;

...

MyType mt;
mt.d = calloc(10,sizeof *mt.d);

以及您将此结构描述为 MPI 类型:

displacements[0] = 0;

MPI_Address(&mt->c, &start_address);
MPI_Address(&mt->a, &address);
displacements[1] = address - start_address;

MPI_Address(&mt->b,&address);
displacements[2] = address-start_address;

MPI_Address(&mt->d, &address);
displacements[3] = address-start_address;

MPI_Type_struct(4,block_lengths, displacements,typelist,MyTypeMPI);

问题是,这个 MPI 结构只会永远应用于您在此处定义中使用的结构的一个实例。你完全无法控制calloc() 决定从哪里获取内存;它可以在虚拟内存中的任何位置。您创建和实例化的下一个类型,d 数组的位移将完全不同;即使使用相同的结构,如果您使用当前realloc() 更改数组的大小mt,它最终可能会有不同的位移。

因此,当您使用其中一种类型发送、接收、减少或其他任何内容时,MPI 库将尽职尽责地进行可能毫无意义的置换,并尝试从那里读取或写入,这可能会导致段错误.

请注意,这不是 MPI 的事情;在使用任何低级通信库,或者尝试从磁盘写出/读入时,您都会遇到同样的问题。

您的选项包括手动将数组“编组”为消息,可以使用其他字段,也可以不使用;或者为 d 的位置添加一些可预测性,例如将其定义为某个已定义的最大大小的数组。

【讨论】:

  • MPI_Pack()MPI_Unpack() 的组合提供了执行可移植数据编组的方法。数据必须在调用 MPI_Reduce() 之前打包,并且自定义操作处理程序必须解包每个数据元素。
猜你喜欢
  • 2019-03-18
  • 2017-11-08
  • 1970-01-01
  • 2020-03-06
  • 1970-01-01
  • 2014-06-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多