MPI_Recv 中的异常情况!接收到的数组最后三个元素错误?

Unusual case in MPI_Recv! The last three elements of received array are wrong?

更新版本

我找到了导致问题的部分,之前在下面进行了解释。我也想和你分享一下情况。我意识到我只犯了一个荒谬的错误。但是,我想知道即使我犯了如下大错误,这个问题是怎么发生的;

我有一个结构定义如下;

#define FP_TYPE double

/* Struct : Nonzero */
struct nonzero{
    int row_index;
    int column_index;
    FP_TYPE value;
};

/* Typedef struct Nonzero */
typedef struct nonzero Nonzero;

我有一个非零数组,要在主处理器的处理器之间分配。为此,我刚刚创建了一个新的数据类型 MY_MPI_NONZERO,如下所示,

#define MPI_FP_TYPE MPI_FLOAT

/**
 * Declare an MPI data type for
 *      + Nonzero Structure
 * */
const int number_of_items = 3;
int block_lengths[3] = {1, 1, 1};
MPI_Datatype data_types[3] = {MPI_INT, MPI_INT, MPI_FP_TYPE};
MPI_Datatype MY_MPI_NONZERO;
MPI_Aint offsets[3];

/* Set Offset Array */
offsets[0] = offsetof(Nonzero, row_index);
offsets[1] = offsetof(Nonzero, column_index);
offsets[2] = offsetof(Nonzero, value);

/* Create the Point Struct and Commit it */
MPI_Type_create_struct(number_of_items, block_lengths,
        offsets, data_types, &MY_MPI_NONZERO);
MPI_Type_commit(&MY_MPI_NONZERO);

最终,我分发了不同的 Nonzeros 数组,如以下问题的旧版本中详细解释的那样。

现在,问题是基于非零结构和非零数据类型的定义。您可能已经意识到,我在定义 Nonzero 数据类型时错误地使用了 MPI_FLOAT,而在 Nonzero 结构中将 double 用于 Nonzero 结构的值成员。这可能是个问题,但是值只是以一种简单的格式从文件中读取的,比如 1.2、2.0 ...这怎么会导致一个大问题,比如发送数组的最后一些部分是错误的?另外,为什么只有最后三个元素是错误的?

问题的旧版本

我只想让主处理器向其他处理器发送不同的数组。每个处理器都知道传入数组的大小,也知道它将向其他处理器发送多少元素。我有一个数组的分散函数,它保持将发送到每个处理器的元素总数如下,

/* Scatter number of nonzeros per each proc */
MPI_Scatter(no_dist_nonzero, 1, MPI_INT,
        &my_no_nonzeros, 1, MPI_INT, MASTER, MPI_COMM_WORLD);

/* Define nonzero array */
if ( my_rank != MASTER )
{
    nonzero = (Nonzero *) malloc(
            sizeof(Nonzero) * my_no_nonzeros);
}


/**
 * Declare an MPI data type for
 *      + Nonzero Structure
 * */
const int number_of_items = 3;
int block_lengths[3] = {1, 1, 1};
MPI_Datatype data_types[3] = {MPI_INT, MPI_INT, MPI_FP_TYPE};
MPI_Datatype MY_MPI_NONZERO;
MPI_Aint offsets[3];

/* Set Offset Array */
offsets[0] = offsetof(Nonzero, row_index);
offsets[1] = offsetof(Nonzero, column_index);
offsets[2] = offsetof(Nonzero, value);

/* Create the Point Struct and Commit it */
MPI_Type_create_struct(number_of_items, block_lengths,
        offsets, data_types, &MY_MPI_NONZERO);
MPI_Type_commit(&MY_MPI_NONZERO);

还有代码的剩余部分如下;

if ( my_rank == MASTER )
{
    int mem_index = 0;
    for ( i = 0; i < comm_size; i++ )
    {
        if ( i != MASTER )
        {
            /* Calculate count and size */
            int sub_count = no_dist_nonzero[i];
            int sub_size = sub_count * sizeof(Nonzero);
            Nonzero *sub_nonzero =
                    (Nonzero *) malloc(sub_size);

            /* Divide nonzero array */
            mem_index += no_dist_nonzero[i-1];
            memcpy(sub_nonzero,
                    nonzero + mem_index, sub_size);


            /* Send nonzeros */
            MPI_Send(sub_nonzero, sub_count,
                    MPI_NONZERO, i,
                    MASTER, MPI_COMM_WORLD);
        }
    }
}else
{
    MPI_Recv(nonzero, my_no_nonzeros, MPI_NONZERO,
            MASTER, MASTER, MPI_COMM_WORLD, MPI_STATUS_IGNORE);

    for ( i = 0; i < my_no_nonzeros; i ++ )
    {
        printf("P[%d] : nonzero[%d] = %.2f\t(%d,%d)\n",
                my_rank, i, nonzero[i].value,
                nonzero[i].row_index, nonzero[i].column_index);
    }
}

这类似于广播,但每个处理器的元素数量不同。现在,当我打印出接收到的元素时,只有每个处理器中每个接收到的数组的最后三个元素 是错误的,例如 0 或不同类型的数字。我创建的一个示例场景用于解释 4 个处理器,主处理器(等级 = 0)除外,其余处理器在其数组中保留 10、11 和 11 个元素,如下所示,

打印语句代表下一行;

处理器[等级]:receivedNonzero[index] = nonzero.value (nonzero.row, nonzero.column)

P[1] : nonzero[0] = 4.00    (5,0)
P[1] : nonzero[1] = 1.00    (5,7)
P[1] : nonzero[2] = 1.00    (6,1)
P[1] : nonzero[3] = 9.00    (6,4)
P[1] : nonzero[4] = 1.00    (7,2)
P[1] : nonzero[5] = 8.00    (7,7)
P[1] : nonzero[6] = 3.00    (8,3)
P[1] : nonzero[7] = 0.00    (8,5)
P[1] : nonzero[8] = 0.00    (1,-2147483648)
P[1] : nonzero[9] = 0.00    (180366288,32731)

P[2] : nonzero[0] = 9.00    (10,2)
P[2] : nonzero[1] = 2.00    (10,3)
P[2] : nonzero[2] = 2.00    (11,5)
P[2] : nonzero[3] = 2.00    (12,0)
P[2] : nonzero[4] = 2.00    (12,7)
P[2] : nonzero[5] = 2.00    (13,1)
P[2] : nonzero[6] = 1.00    (13,3)
P[2] : nonzero[7] = 6.00    (13,5)
P[2] : nonzero[8] = 0.00    (14,32715)
P[2] : nonzero[9] = 0.00    (1215315376,32715)
P[2] : nonzero[10] = 0.00   (1215319296,32715)

P[3] : nonzero[0] = 4.00    (15,0)
P[3] : nonzero[1] = 2.00    (15,4)
P[3] : nonzero[2] = 2.00    (16,6)
P[3] : nonzero[3] = 3.00    (17,0)
P[3] : nonzero[4] = 7.00    (17,3)
P[3] : nonzero[5] = 9.00    (18,1)
P[3] : nonzero[6] = 3.00    (18,4)
P[3] : nonzero[7] = 3.00    (18,7)
P[3] : nonzero[8] = 1141143300351626597783743016932944640301310822732232512436170973423802137351962278027655782681814493455862954554635505069706412465354938627437900810355923222434815569775088619100027795823768424096546808505779224664332855111823098875222717104128.00 (19,1645150208)
P[3] : nonzero[9] = 0.00    (825110830,302186544)
P[3] : nonzero[10] = 0.00   (1,8108)

你知道这种情况出了什么问题吗?即使我在 MPI_Send() 之前打印出 sub_nonzero 数组来检查将数组拆分为子数组是否有错误;没有错...

导致错误的原因是 float / double 的数据大小不匹配。使用此错误信息,MPI 将数据写入内存中的错误位置。

这基本上是未定义的行为,任何事情都可能发生,包括鼻恶魔。为什么 - 在实践中 - 只有数据的尾端是错误的,原因如下。 MPI 实际上并不一定关心字节的个别含义。由于数据是连续的,它只关心整体大小——所以它只是传输太少的数据。您在发送方和接收方以相同的方式解释数据,传输的部分看起来很好。

我忍不住要提到这是一个很好的例子,在您的问题中包含 Minimal, Complete, and Verifiable example 是多么重要。