MPI 派生数据类型适用于浮点数,但不适用于双精度数。是对齐问题吗?
MPI derived datatype works for floats, but not for doubles. Is it an alignment issue?
我有一个与 C 结构相关的奇怪问题,该结构是在 MPI 派生数据类型的帮助下进行通信的。下面的例子有效;它只是发送一条消息,其中包含一个 integer
加上 4 个 float
值。
最小工作示例:
#include <mpi.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
MPI_Init(&argc, &argv);
int i, rank, tag = 1;
MPI_Status status;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
// Array of doubles plus element count
typedef struct {
int row;
float elements[4];
} My_array;
// Derived datatype for an array of doubles plus element count
MPI_Datatype MY_ARRAY_TYPE;
const int nr_blocks = 2;
int blocklengths[2] = {1, 4};
MPI_Datatype oldtypes[2] = {MPI_INT, MPI_FLOAT};
MPI_Aint extent, lb;
MPI_Type_get_extent(MPI_INT, &lb, &extent);
MPI_Aint displacements[2] = {0, extent};
MPI_Type_create_struct(nr_blocks, blocklengths, displacements,
oldtypes, &MY_ARRAY_TYPE);
MPI_Type_commit(&MY_ARRAY_TYPE);
if(rank == 0) {
My_array array1 = {3, 3.1, 3.2, 3.3, 3.4};
MPI_Send(&array1, 1, MY_ARRAY_TYPE, 1, tag, MPI_COMM_WORLD);
}
if(rank == 1) {
My_array array2;
MPI_Recv(&array2, 1, MY_ARRAY_TYPE, 0, tag, MPI_COMM_WORLD, &status);
printf("Rank %d received elements of row %d:\n", rank, array2.row);
for(i = 0; i < 4; i++)
printf("\t%.1f\n", array2.elements[i]);
}
MPI_Type_free(&MY_ARRAY_TYPE);
MPI_Finalize();
}
如果您可以访问 MPI 安装,则示例可以由 mpicc -o example example.c
和 运行 由 mpirun -np 2 example
编译。
输出应该是
Rank 1 received elements of row 3:
3.1
3.2
3.3
3.4
问题:
现在,当 float
s 的数组更改为 double
s 的数组,相应地 MPI_FLOAT
到 MPI_DOUBLE
,我得到一个错误的结果。
此代码:
#include <mpi.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
MPI_Init(&argc, &argv);
int i, rank, tag = 1;
MPI_Status status;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
// Array of doubles plus element count
typedef struct {
int row;
double elements[4];
} My_array;
// Derived datatype for an array of doubles plus element count
MPI_Datatype MY_ARRAY_TYPE;
const int nr_blocks = 2;
int blocklengths[2] = {1, 4};
MPI_Datatype oldtypes[2] = {MPI_INT, MPI_DOUBLE};
MPI_Aint extent, lb;
MPI_Type_get_extent(MPI_INT, &lb, &extent);
MPI_Aint displacements[2] = {0, extent};
MPI_Type_create_struct(nr_blocks, blocklengths, displacements,
oldtypes, &MY_ARRAY_TYPE);
MPI_Type_commit(&MY_ARRAY_TYPE);
if(rank == 0) {
My_array array1 = {3, 3.1, 3.2, 3.3, 3.4};
MPI_Send(&array1, 1, MY_ARRAY_TYPE, 1, tag, MPI_COMM_WORLD);
}
if(rank == 1) {
My_array array2;
MPI_Recv(&array2, 1, MY_ARRAY_TYPE, 0, tag, MPI_COMM_WORLD, &status);
printf("Rank %d received elements of row %d:\n", rank, array2.row);
for(i = 0; i < 4; i++)
printf("\t%.1f\n", array2.elements[i]);
}
MPI_Type_free(&MY_ARRAY_TYPE);
MPI_Finalize();
}
产生:
Rank 1 received elements of row 3:
3.1
3.2
3.3
0.0
我试了一下,使用结构中的其他数据和派生数据类型(例如,一个整数数组而不是一个整数,int
/MPI_INT
而不是 float
/MPI_FLOAT
, 等) 并看到只有在使用双打时才会出现问题。这让我怀疑这可能是某种对齐问题 - 但我被困在那里。 MPI 应该自动处理对齐。
问题:为什么上面的例子对float
/MPI_FLOAT
有效,但对double
/MPI_DOUBLE
无效我该如何解决?
一些可能相关的机器细节:
- CPU:AMD 皓龙 6134
- 地址大小:48 位
- 对齐:64
- 编译器:gcc 4.4.7
- MPI 库:(不幸的是)供应商特定
编辑:按照 Vladimir F 评论中的建议,我添加了不起作用的代码。
我才知道是什么问题:确实是对齐问题。第二个代码清单正确地产生了前 3 个双精度数不过是一个奇怪的巧合......通过使用 MPI_INT
的扩展作为以下值的偏移量,我假设不会有填充。最好像这样计算偏移量:
#include <stddef.c>
...
MPI_Datatype MY_ARRAY_TYPE;
const int nr_blocks = 2;
int blocklengths[2] = {1, 4};
MPI_Datatype oldtypes[2] = {MPI_INT, MPI_DOUBLE};
MPI_Aint displacements[2];
displacements[0] = offsetof(My_array, row);
displacements[1] = offsetof(My_array, elements);
MPI_Type_create_struct(nr_blocks, blocklengths, displacements,
oldtypes, &MY_ARRAY_TYPE);
MPI_Type_commit(&MY_ARRAY_TYPE);
...
我真的很想知道它是如何以这种方式计算出来的...为什么我们得到 3 个正确的值和一个 0.0?由于对齐偏移了 4 个字节,而双精度数在我的平台上用 8 个字节表示,为什么我没有得到一些随机数?如果前 3 个都取一个 double 的低 4 字节加上下一个 double 的高 4 字节,怎么能正确解码?
我有一个与 C 结构相关的奇怪问题,该结构是在 MPI 派生数据类型的帮助下进行通信的。下面的例子有效;它只是发送一条消息,其中包含一个 integer
加上 4 个 float
值。
最小工作示例:
#include <mpi.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
MPI_Init(&argc, &argv);
int i, rank, tag = 1;
MPI_Status status;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
// Array of doubles plus element count
typedef struct {
int row;
float elements[4];
} My_array;
// Derived datatype for an array of doubles plus element count
MPI_Datatype MY_ARRAY_TYPE;
const int nr_blocks = 2;
int blocklengths[2] = {1, 4};
MPI_Datatype oldtypes[2] = {MPI_INT, MPI_FLOAT};
MPI_Aint extent, lb;
MPI_Type_get_extent(MPI_INT, &lb, &extent);
MPI_Aint displacements[2] = {0, extent};
MPI_Type_create_struct(nr_blocks, blocklengths, displacements,
oldtypes, &MY_ARRAY_TYPE);
MPI_Type_commit(&MY_ARRAY_TYPE);
if(rank == 0) {
My_array array1 = {3, 3.1, 3.2, 3.3, 3.4};
MPI_Send(&array1, 1, MY_ARRAY_TYPE, 1, tag, MPI_COMM_WORLD);
}
if(rank == 1) {
My_array array2;
MPI_Recv(&array2, 1, MY_ARRAY_TYPE, 0, tag, MPI_COMM_WORLD, &status);
printf("Rank %d received elements of row %d:\n", rank, array2.row);
for(i = 0; i < 4; i++)
printf("\t%.1f\n", array2.elements[i]);
}
MPI_Type_free(&MY_ARRAY_TYPE);
MPI_Finalize();
}
如果您可以访问 MPI 安装,则示例可以由 mpicc -o example example.c
和 运行 由 mpirun -np 2 example
编译。
输出应该是
Rank 1 received elements of row 3:
3.1
3.2
3.3
3.4
问题:
现在,当 float
s 的数组更改为 double
s 的数组,相应地 MPI_FLOAT
到 MPI_DOUBLE
,我得到一个错误的结果。
此代码:
#include <mpi.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
MPI_Init(&argc, &argv);
int i, rank, tag = 1;
MPI_Status status;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
// Array of doubles plus element count
typedef struct {
int row;
double elements[4];
} My_array;
// Derived datatype for an array of doubles plus element count
MPI_Datatype MY_ARRAY_TYPE;
const int nr_blocks = 2;
int blocklengths[2] = {1, 4};
MPI_Datatype oldtypes[2] = {MPI_INT, MPI_DOUBLE};
MPI_Aint extent, lb;
MPI_Type_get_extent(MPI_INT, &lb, &extent);
MPI_Aint displacements[2] = {0, extent};
MPI_Type_create_struct(nr_blocks, blocklengths, displacements,
oldtypes, &MY_ARRAY_TYPE);
MPI_Type_commit(&MY_ARRAY_TYPE);
if(rank == 0) {
My_array array1 = {3, 3.1, 3.2, 3.3, 3.4};
MPI_Send(&array1, 1, MY_ARRAY_TYPE, 1, tag, MPI_COMM_WORLD);
}
if(rank == 1) {
My_array array2;
MPI_Recv(&array2, 1, MY_ARRAY_TYPE, 0, tag, MPI_COMM_WORLD, &status);
printf("Rank %d received elements of row %d:\n", rank, array2.row);
for(i = 0; i < 4; i++)
printf("\t%.1f\n", array2.elements[i]);
}
MPI_Type_free(&MY_ARRAY_TYPE);
MPI_Finalize();
}
产生:
Rank 1 received elements of row 3:
3.1
3.2
3.3
0.0
我试了一下,使用结构中的其他数据和派生数据类型(例如,一个整数数组而不是一个整数,int
/MPI_INT
而不是 float
/MPI_FLOAT
, 等) 并看到只有在使用双打时才会出现问题。这让我怀疑这可能是某种对齐问题 - 但我被困在那里。 MPI 应该自动处理对齐。
问题:为什么上面的例子对float
/MPI_FLOAT
有效,但对double
/MPI_DOUBLE
无效我该如何解决?
一些可能相关的机器细节:
- CPU:AMD 皓龙 6134
- 地址大小:48 位
- 对齐:64
- 编译器:gcc 4.4.7
- MPI 库:(不幸的是)供应商特定
编辑:按照 Vladimir F 评论中的建议,我添加了不起作用的代码。
我才知道是什么问题:确实是对齐问题。第二个代码清单正确地产生了前 3 个双精度数不过是一个奇怪的巧合......通过使用 MPI_INT
的扩展作为以下值的偏移量,我假设不会有填充。最好像这样计算偏移量:
#include <stddef.c>
...
MPI_Datatype MY_ARRAY_TYPE;
const int nr_blocks = 2;
int blocklengths[2] = {1, 4};
MPI_Datatype oldtypes[2] = {MPI_INT, MPI_DOUBLE};
MPI_Aint displacements[2];
displacements[0] = offsetof(My_array, row);
displacements[1] = offsetof(My_array, elements);
MPI_Type_create_struct(nr_blocks, blocklengths, displacements,
oldtypes, &MY_ARRAY_TYPE);
MPI_Type_commit(&MY_ARRAY_TYPE);
...
我真的很想知道它是如何以这种方式计算出来的...为什么我们得到 3 个正确的值和一个 0.0?由于对齐偏移了 4 个字节,而双精度数在我的平台上用 8 个字节表示,为什么我没有得到一些随机数?如果前 3 个都取一个 double 的低 4 字节加上下一个 double 的高 4 字节,怎么能正确解码?