序列化嵌套结构以在 MPI 中发送/接收

Serializing a Nested Struct to send/recv in MPI

提问人:Max Rush 提问时间:3/26/2023 更新时间:3/26/2023 访问量:111

问:

基本上,我正在创建一个并行程序来计算 +50000 x 50000 像素的 julia 集图像,并且我正在使用 MPI 和 PNG lib 来执行此操作。我有一个结构

typedef struct Block
{
    int size;
    int place;
    png_bytep *rows;
} block;

block knapsack;
png_bytep *row_pointers;

和 allocate 函数

void allocate()
{
    row_pointers = (png_bytep *)malloc(sizeof(png_bytep) * height);
    for (y = 0; y < height; y++)
        row_pointers[y] = (png_byte *)malloc(sizeof(png_bytep) * width);
}

我有这个功能来创建一个“背包”,这是一个可以分发到其他进程的row_pointers块。(我稍后在解决此消息传递问题时将合并这些函数。

void pack(int index, int size)
{
    knapsack.rows = (png_bytep *)malloc(sizeof(png_bytep) * size);
    knapsack.size = size;
    knapsack.place = index;

    for (y = 0; y < size; y++)
    {
        knapsack.rows[y] = (png_byte *)malloc(sizeof(png_bytep) * width);
        knapsack.rows[y] = row_pointers[index + y];
    }
}

然后我想做一些类似的事情

MPI_Send(&knapsack, sizeof(knapsack), MPI_BYTE, 1, 1, MPI_COMM_WORLD);


MPI_Recv(&knapsack, sizeof(knapsack), MPI_BYTE, 0, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);

将这些指针的块发送到一堆节点进行计算。它最终将在 128 个内核上进行测试。 问题是我在系统周围发送嵌套结构时遇到了很多麻烦,并且在使用 printf(“%x”, knapsack.rows[0]) 打印出内容后;我可以看到它们在发送后不匹配。我一直在研究我并意识到因为我的指针不在连续块中,所以它没有正确发送。我一直在考虑序列化我的背包,并遇到了 flatbuffers 和 protocol buffers。这些似乎过于复杂,很难找到一个好的教程。我的另一个选择似乎是 MPI_Pack(),但我不确定时间增加会有多糟糕,因为整个目标是尽可能高地推动它并快速完成。有没有人对解决这个问题的最佳方法有任何建议?谢谢!

C 序列化 MPI FlatBuffers

评论


答:

1赞 Craig Estey 3/26/2023 #1

几个问题......

您没有真正的 2D 数组。您有一个指向行的一维指针数组,这些行是像素的一维数组(例如 是指向像素数组的指针)。png_byteppng_byte

由于额外的指针间接,这可能会很慢。除非您的函数严格地对行进行操作。

拥有真正的 2D 像素数组可能更好(例如):

typedef struct Block {
    int size;
    int place;
    int width;
    int height;
    png_byte *data;
} block;

block knapsack;
png_byte *img_data;

void
allocate(int height,int width)
{

    knapsack.height = height;
    knapsack.width = width;
    knapsack.size = sizeof(png_byte) * height * width;

    knapsack.data = malloc(knapsack.size);
    img_data = knapsack.data;
}

您的 [和 ] 只需发送 .MPI_SendMPI_Recvstruct

但是,在 [非零] 秩收到指针后,指针毫无意义,因为它是秩 0 进程的地址空间内的地址。png_byte *data;

发送结构告诉接收者几何形状(即高度/宽度)和其他元数据,但它不会发送实际数据。

尽管[可能]有一些更高级的调用,但这里有一些使用简单...MPI_*MPI_Send/MPI_Recv

寄件人:

// send geometry and metadata (the .data pointer will be useless to receiver,
// but that's okay)
MPI_Send(&knapsack, sizeof(knapsack), MPI_BYTE, 1, 1, MPI_COMM_WORLD);

// send the data matrix
MPI_Send(knapsack.data, knapsack.size, MPI_BYTE, 1, 1, MPI_COMM_WORLD);

接收器:

// receive geometry and metadata (the .data pointer will be useless to us,
// but that's okay)
MPI_Recv(&knapsack, sizeof(knapsack), MPI_BYTE, 1, 1, MPI_COMM_WORLD);

// allocate space to receive the data
knapsack.data = malloc(knapsack.size);

// receive the data matrix
MPI_Receive(knapsack.data, knapsack.size, MPI_BYTE, 1, 1, MPI_COMM_WORLD);

上面假设您要将整个数组发送到每个 [worker] 等级。这是最简单的。

但是,在你完成这项工作之后,你可能想改用它来向每个工人等级发送部分/子矩阵。也就是说,每个排名仅在完整矩阵的 2D 子窗口上运行。MPI_Scatter/MPI_Gather

当然,您仍然可以使用指针,但传输的实际调用需要是一个循环,每个循环都有一个单独的调用knapsack.datarow


有关如何拆分数据以提高性能的其他信息,请参阅我最近的[!] answer: 在 MPI 中发送自定义结构

旁注:如果您的节点将位于同一物理处理器系统上(例如,您有一台 128 核计算机),并且所有内核都可以映射/共享相同的内存,则最好使用 或 。当然,开销会更少,并且程序可能对缓存更友好。YMMV ...pthreadsopenmp

评论

0赞 Max Rush 3/27/2023
谢谢!经过几个小时的调试,终于得到了一个可行的解决方案。也会尝试您关于分散和收集的建议。下一组是负载均衡。不幸的是,我们不能使用 pthreads,也许可以使用 openmp,因为它将在集群上运行。应该更仔细地阅读第一句话,因为这就是我意识到问题的方式哈哈