发送具有 std::vector 成员的结构时出现分段错误
Segmentation fault when sending struct having std::vector member
为什么我使用 mpirun -np 2 ./out
命令的以下代码会出现以下错误?我在调整 std::vector
的大小后调用了 make_layout()
所以通常我不应该得到这个错误。如果我不调整大小,它会起作用。这是什么原因?
main.cpp:
#include <iostream>
#include <vector>
#include "mpi.h"
MPI_Datatype MPI_CHILD;
struct Child
{
std::vector<int> age;
void make_layout();
};
void Child::make_layout()
{
int nblock = 1;
int age_size = age.size();
int block_count[nblock] = {age_size};
MPI_Datatype block_type[nblock] = {MPI_INT};
MPI_Aint offset[nblock] = {0};
MPI_Type_struct(nblock, block_count, offset, block_type, &MPI_CHILD);
MPI_Type_commit(&MPI_CHILD);
}
int main()
{
int rank, size;
MPI_Init(NULL, NULL);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);
Child kid;
kid.age.resize(5);
kid.make_layout();
int datasize;
MPI_Type_size(MPI_CHILD, &datasize);
std::cout << datasize << std::endl; // output: 20 (5x4 seems OK).
if (rank == 0)
{
MPI_Send(&kid, 1, MPI_CHILD, 1, 0, MPI_COMM_WORLD);
}
if (rank == 1)
{
MPI_Recv(&kid, 1, MPI_CHILD, 0, 0, MPI_COMM_WORLD, NULL);
}
MPI_Finalize();
return 0;
}
错误信息:
*** Process received signal ***
Signal: Segmentation fault (11)
Signal code: Address not mapped (1)
Failing at address: 0x14ae7b8
[ 0] /lib/x86_64-linux-gnu/libpthread.so.0(+0x113d0)[0x7fe1ad91c3d0]
[ 1] /lib/x86_64-linux-gnu/libc.so.6(cfree+0x22)[0x7fe1ad5c5a92]
[ 2] ./out[0x400de4]
[ 3] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7fe1ad562830]
[ 4] ./out[0x400ec9]
*** End of error message ***
这里的问题是您告诉 MPI 从 &kid
发送一个整数块,但这不是您的数据所在的位置。 &kid
指向一个 std::vector
对象,它有一个内部指针指向分配在堆上某处的整数块。
将 &kid
替换为 kid.age.data()
,它应该可以工作。当您不调整大小时 "works" 的原因是向量的大小为 0,因此 MPI 将尝试发送空消息并且不会发生实际的内存访问。
小心,您遇到了几个问题。
首先 std::vector
将对象存储在堆中,因此数据并未真正存储在您的结构中。
其次你甚至不能在动态库之间发送STL容器,对于应用程序实例也是如此。因为它们可能是用不同版本的 STL 编译的,并且在不同的体系结构上工作的方式不同。
关于这部分问题,这里有一个很好的答案:
这里有几个 std::vector
成员的例子,它们使用带绝对地址的 MPI 数据类型:
struct Child
{
int foo;
std::vector<float> bar;
std::vector<int> baz;
Child() : dtype(MPI_DATATYPE_NULL) {}
~Child() { if (dtype != MPI_DATATYPE_NULL) MPI_Type_free(dtype); }
const MPI_Datatype mpi_dtype();
void invalidate_dtype();
private:
MPI_Datatype dtype;
void make_dtype();
};
const MPI_Datatype Child::mpi_dtype()
{
if (dtype == MPI_DATATYPE_NULL)
make_dtype();
return dtype;
}
void Child::invalidate_dtype()
{
if (dtype != MPI_DATATYPE_NULL)
MPI_Datatype_free(&dtype);
}
void Child::make_dtype()
{
const int nblock = 3;
int block_count[nblock] = {1, bar.size(), baz.size()};
MPI_Datatype block_type[nblock] = {MPI_INT, MPI_FLOAT, MPI_INT};
MPI_Aint offset[nblock];
MPI_Get_address(&foo, &offset[0]);
MPI_Get_address(&bar[0], &offset[1]);
MPI_Get_address(&baz[0], &offset[2]);
MPI_Type_struct(nblock, block_count, offset, block_type, &dtype);
MPI_Type_commit(&dtype);
}
class的示例使用:
Child kid;
kid.foo = 5;
kid.bar.resize(5);
kid.baz.resize(10);
if (rank == 0)
{
MPI_Send(MPI_BOTTOM, 1, kid.mpi_dtype(), 1, 0, MPI_COMM_WORLD);
}
if (rank == 1)
{
MPI_Recv(MPI_BOTTOM, 1, kid.mpi_dtype(), 0, 0, MPI_COMM_WORLD, NULL);
}
注意使用MPI_BOTTOM
作为缓冲区地址。 MPI_BOTTOM
指定地址 space 的底部,在平面地址 space 的体系结构上为 0
。由于传递给 MPI_Type_create_struct
的偏移量是结构成员的绝对地址,因此当将它们添加到 0
时,结果又是每个结构成员的绝对地址。 Child::mpi_dtype()
returns 特定于该实例的延迟构建的 MPI 数据类型。
由于 resize()
重新分配内存,这可能导致数据被移动到内存中的不同位置,因此应该使用 invalidate_dtype()
方法在 [= 之后强制重新创建 MPI 数据类型20=] 或任何其他可能触发内存重新分配的操作:
// ...
kid.bar.resize(100);
kid.invalidate_dtype();
// MPI_Send / MPI_Recv
请原谅上面任何草率的 C++ 代码。
为什么我使用 mpirun -np 2 ./out
命令的以下代码会出现以下错误?我在调整 std::vector
的大小后调用了 make_layout()
所以通常我不应该得到这个错误。如果我不调整大小,它会起作用。这是什么原因?
main.cpp:
#include <iostream>
#include <vector>
#include "mpi.h"
MPI_Datatype MPI_CHILD;
struct Child
{
std::vector<int> age;
void make_layout();
};
void Child::make_layout()
{
int nblock = 1;
int age_size = age.size();
int block_count[nblock] = {age_size};
MPI_Datatype block_type[nblock] = {MPI_INT};
MPI_Aint offset[nblock] = {0};
MPI_Type_struct(nblock, block_count, offset, block_type, &MPI_CHILD);
MPI_Type_commit(&MPI_CHILD);
}
int main()
{
int rank, size;
MPI_Init(NULL, NULL);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);
Child kid;
kid.age.resize(5);
kid.make_layout();
int datasize;
MPI_Type_size(MPI_CHILD, &datasize);
std::cout << datasize << std::endl; // output: 20 (5x4 seems OK).
if (rank == 0)
{
MPI_Send(&kid, 1, MPI_CHILD, 1, 0, MPI_COMM_WORLD);
}
if (rank == 1)
{
MPI_Recv(&kid, 1, MPI_CHILD, 0, 0, MPI_COMM_WORLD, NULL);
}
MPI_Finalize();
return 0;
}
错误信息:
*** Process received signal ***
Signal: Segmentation fault (11)
Signal code: Address not mapped (1)
Failing at address: 0x14ae7b8
[ 0] /lib/x86_64-linux-gnu/libpthread.so.0(+0x113d0)[0x7fe1ad91c3d0]
[ 1] /lib/x86_64-linux-gnu/libc.so.6(cfree+0x22)[0x7fe1ad5c5a92]
[ 2] ./out[0x400de4]
[ 3] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7fe1ad562830]
[ 4] ./out[0x400ec9]
*** End of error message ***
这里的问题是您告诉 MPI 从 &kid
发送一个整数块,但这不是您的数据所在的位置。 &kid
指向一个 std::vector
对象,它有一个内部指针指向分配在堆上某处的整数块。
将 &kid
替换为 kid.age.data()
,它应该可以工作。当您不调整大小时 "works" 的原因是向量的大小为 0,因此 MPI 将尝试发送空消息并且不会发生实际的内存访问。
小心,您遇到了几个问题。
首先 std::vector
将对象存储在堆中,因此数据并未真正存储在您的结构中。
其次你甚至不能在动态库之间发送STL容器,对于应用程序实例也是如此。因为它们可能是用不同版本的 STL 编译的,并且在不同的体系结构上工作的方式不同。
关于这部分问题,这里有一个很好的答案:
这里有几个 std::vector
成员的例子,它们使用带绝对地址的 MPI 数据类型:
struct Child
{
int foo;
std::vector<float> bar;
std::vector<int> baz;
Child() : dtype(MPI_DATATYPE_NULL) {}
~Child() { if (dtype != MPI_DATATYPE_NULL) MPI_Type_free(dtype); }
const MPI_Datatype mpi_dtype();
void invalidate_dtype();
private:
MPI_Datatype dtype;
void make_dtype();
};
const MPI_Datatype Child::mpi_dtype()
{
if (dtype == MPI_DATATYPE_NULL)
make_dtype();
return dtype;
}
void Child::invalidate_dtype()
{
if (dtype != MPI_DATATYPE_NULL)
MPI_Datatype_free(&dtype);
}
void Child::make_dtype()
{
const int nblock = 3;
int block_count[nblock] = {1, bar.size(), baz.size()};
MPI_Datatype block_type[nblock] = {MPI_INT, MPI_FLOAT, MPI_INT};
MPI_Aint offset[nblock];
MPI_Get_address(&foo, &offset[0]);
MPI_Get_address(&bar[0], &offset[1]);
MPI_Get_address(&baz[0], &offset[2]);
MPI_Type_struct(nblock, block_count, offset, block_type, &dtype);
MPI_Type_commit(&dtype);
}
class的示例使用:
Child kid;
kid.foo = 5;
kid.bar.resize(5);
kid.baz.resize(10);
if (rank == 0)
{
MPI_Send(MPI_BOTTOM, 1, kid.mpi_dtype(), 1, 0, MPI_COMM_WORLD);
}
if (rank == 1)
{
MPI_Recv(MPI_BOTTOM, 1, kid.mpi_dtype(), 0, 0, MPI_COMM_WORLD, NULL);
}
注意使用MPI_BOTTOM
作为缓冲区地址。 MPI_BOTTOM
指定地址 space 的底部,在平面地址 space 的体系结构上为 0
。由于传递给 MPI_Type_create_struct
的偏移量是结构成员的绝对地址,因此当将它们添加到 0
时,结果又是每个结构成员的绝对地址。 Child::mpi_dtype()
returns 特定于该实例的延迟构建的 MPI 数据类型。
由于 resize()
重新分配内存,这可能导致数据被移动到内存中的不同位置,因此应该使用 invalidate_dtype()
方法在 [= 之后强制重新创建 MPI 数据类型20=] 或任何其他可能触发内存重新分配的操作:
// ...
kid.bar.resize(100);
kid.invalidate_dtype();
// MPI_Send / MPI_Recv
请原谅上面任何草率的 C++ 代码。