You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MPI发送含嵌套动态数组的结构体,有无更简洁实现方案?

更简洁的MPI嵌套动态结构体传输方案

问题背景

我在C语言中使用MPI开发,需要发送一个包含子结构体动态数组的结构体,子结构体还嵌套了char类型动态数组。目前已实现先发送动态数据大小再传输数据的方案,但觉得该方案较复杂,想了解是否有更简洁的实现方式。现有代码如下:

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

typedef struct Code {
  char character;
  int length;
  char *code;
} Code;

typedef struct CodeDictionary {
  int codesNr;
  Code *codes;
} CodeDictionary;

static const int CODE_NR = 5;

void buildCodeType(MPI_Datatype *codeType) {
    int blockLen[2] = {1, 1};
    MPI_Datatype types[2] = {MPI_CHAR, MPI_INT};
    
    MPI_Aint offsets[2];
    offsets[0] = offsetof(Code, character);
    offsets[1] = offsetof(Code, length);

    MPI_Type_create_struct(2, blockLen, offsets, types, codeType);
    MPI_Type_commit(codeType);
}

int getRand(int from, int to)
{
    int num = (rand() % (to - from + 1)) + from;
    return num;
}

int main(int argc,char *argv[]) {
    MPI_Init(NULL, NULL);

    int procNumber;
    int pid; 

    MPI_Comm_size(MPI_COMM_WORLD, &procNumber);
    MPI_Comm_rank(MPI_COMM_WORLD, &pid);

    if (procNumber < 2) {
        printf("At least two processes required.\n");
        return 1;
    }

    const int from = 1;
    const int to = 9;

    srand(time(0));

    MPI_Datatype codeType;
    buildCodeType(&codeType);

    if (pid == 0) {
        CodeDictionary sndDict = {.codesNr = CODE_NR, .codes = calloc(CODE_NR, sizeof(Code))};

        // create some fake values
        for (int i = 0; i < CODE_NR; i++) {
            sndDict.codes[i].character = 'a' + i;
            sndDict.codes[i].length = getRand(from, to);
            sndDict.codes[i].code = calloc(sndDict.codes[i].length, sizeof(char));

            for (int j = 0; j < sndDict.codes[i].length; j++) {
                int randChar = getRand('a', 'z');
                sndDict.codes[i].code[j] = randChar + j;
            }
        }

        printf("source data\n");

        for (int i = 0; i < sndDict.codesNr; i++) {
            printf("codes[%d]:\n\t", i);

            printf("character: %c\tlength: %d\tcode: ", sndDict.codes[i].character, sndDict.codes[i].length);
            for (int j = 0; j < sndDict.codes[i].length; j++)
                printf("%c", sndDict.codes[i].code[j]);

            printf("\n");
        }

        for (int i = 1; i < procNumber; i++) { // maybe I can use MPI_Bcast
            MPI_Send(&sndDict.codesNr, 1, MPI_INT, i, 0, MPI_COMM_WORLD);

            for (int j = 0; j < sndDict.codesNr; j++) {
                MPI_Send(&sndDict.codes[j], 1, codeType, i, 0, MPI_COMM_WORLD);
                MPI_Send(sndDict.codes[j].code, sndDict.codes[j].length, MPI_CHAR, i, 0, MPI_COMM_WORLD);
            }
        }

        for (int i = 0; i < sndDict.codesNr; i++)
            free(sndDict.codes[i].code);

    } else {
        MPI_Status status;
        CodeDictionary rcvDict = {.codesNr = 0, .codes = NULL};

        MPI_Recv(&rcvDict.codesNr, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &status);

        rcvDict.codes = calloc(rcvDict.codesNr, sizeof(Code));
        for (int i = 0; i < rcvDict.codesNr; i++) {
            MPI_Recv(&rcvDict.codes[i], 1, codeType, 0, 0, MPI_COMM_WORLD, &status);
            
            rcvDict.codes[i].code = calloc(rcvDict.codes[i].length, sizeof(char));
            MPI_Recv(rcvDict.codes[i].code, rcvDict.codes[i].length, MPI_CHAR, 0, 0, MPI_COMM_WORLD, &status);
        }

       printf("\nreceived data\n");

        for (int i = 0; i < rcvDict.codesNr; i++) {
            printf("codes[%d]:\n\t", i);

            printf("character: %c\tlength: %d\tcode: ", rcvDict.codes[i].character, rcvDict.codes[i].length);
            for (int j = 0; j < rcvDict.codes[i].length; j++)
                printf("%c", rcvDict.codes[i].code[j]);

            printf("\n");
        }

        for (int i = 0; i < rcvDict.codesNr; i++)
            free(rcvDict.codes[i].code);
    }

    MPI_Type_free(&codeType);
    MPI_Finalize();

    return 0;
}

优化方案

方案1:用MPI_Bcast替代循环发送

如果是向所有进程广播数据,直接用MPI_Bcast代替循环给每个进程发送,能大幅简化发送端代码,同时避免重复的通信调用:

发送端(pid=0)修改:

// 替换原来的循环发送逻辑
MPI_Bcast(&sndDict.codesNr, 1, MPI_INT, 0, MPI_COMM_WORLD);
for (int j = 0; j < sndDict.codesNr; j++) {
    MPI_Bcast(&sndDict.codes[j], 1, codeType, 0, MPI_COMM_WORLD);
    MPI_Bcast(sndDict.codes[j].code, sndDict.codes[j].length, MPI_CHAR, 0, MPI_COMM_WORLD);
}

接收端(pid!=0)修改:

// 替换原来的循环接收逻辑
MPI_Bcast(&rcvDict.codesNr, 1, MPI_INT, 0, MPI_COMM_WORLD);
rcvDict.codes = calloc(rcvDict.codesNr, sizeof(Code));
for (int i = 0; i < rcvDict.codesNr; i++) {
    MPI_Bcast(&rcvDict.codes[i], 1, codeType, 0, MPI_COMM_WORLD);
    rcvDict.codes[i].code = calloc(rcvDict.codes[i].length, sizeof(char));
    MPI_Bcast(rcvDict.codes[i].code, rcvDict.codes[i].length, MPI_CHAR, 0, MPI_COMM_WORLD);
}

方案2:使用MPI_Pack/MPI_Unpack合并传输

将所有数据打包到一个连续缓冲区,一次性完成广播/发送,减少通信次数,代码更紧凑:

发送端(pid=0)完整修改:

if (pid == 0) {
    CodeDictionary sndDict = {.codesNr = CODE_NR, .codes = calloc(CODE_NR, sizeof(Code))};

    // 生成测试数据(逻辑不变)
    for (int i = 0; i < CODE_NR; i++) {
        sndDict.codes[i].character = 'a' + i;
        sndDict.codes[i].length = getRand(from, to);
        sndDict.codes[i].code = calloc(sndDict.codes[i].length, sizeof(char));

        for (int j = 0; j < sndDict.codes[i].length; j++) {
            int randChar = getRand('a', 'z');
            sndDict.codes[i].code[j] = randChar + j;
        }
    }

    // 打印源数据(逻辑不变)
    printf("source data\n");
    for (int i = 0; i < sndDict.codesNr; i++) {
        printf("codes[%d]:\n\t", i);
        printf("character: %c\tlength: %d\tcode: ", sndDict.codes[i].character, sndDict.codes[i].length);
        for (int j = 0; j < sndDict.codes[i].length; j++)
            printf("%c", sndDict.codes[i].code[j]);
        printf("\n");
    }

    // 计算总打包数据量
    int total_size = sizeof(int); // codesNr的大小
    for (int i = 0; i < sndDict.codesNr; i++) {
        total_size += sizeof(char) + sizeof(int); // character + length
        total_size += sndDict.codes[i].length * sizeof(char); // code数组
    }

    // 分配打包缓冲区
    char *buffer = malloc(total_size);
    int position = 0;

    // 打包所有数据
    MPI_Pack(&sndDict.codesNr, 1, MPI_INT, buffer, total_size, &position, MPI_COMM_WORLD);
    for (int i = 0; i < sndDict.codesNr; i++) {
        MPI_Pack(&sndDict.codes[i].character, 1, MPI_CHAR, buffer, total_size, &position, MPI_COMM_WORLD);
        MPI_Pack(&sndDict.codes[i].length, 1, MPI_INT, buffer, total_size, &position, MPI_COMM_WORLD);
        MPI_Pack(sndDict.codes[i].code, sndDict.codes[i].length, MPI_CHAR, buffer, total_size, &position, MPI_COMM_WORLD);
    }

    // 广播数据:先广播总大小,再广播缓冲区
    MPI_Bcast(&total_size, 1, MPI_INT, 0, MPI_COMM_WORLD);
    MPI_Bcast(buffer, total_size, MPI_PACKED, 0, MPI_COMM_WORLD);

    // 释放资源
    free(buffer);
    for (int i = 0; i < sndDict.codesNr; i++)
        free(sndDict.codes[i].code);
}

接收端(pid!=0)完整修改:

} else {
    CodeDictionary rcvDict = {.codesNr = 0, .codes = NULL};
    int total_size;

    // 接收总数据大小
    MPI_Bcast(&total_size, 1, MPI_INT, 0, MPI_COMM_WORLD);
    char *buffer = malloc(total_size);
    // 接收打包缓冲区
    MPI_Bcast(buffer, total_size, MPI_PACKED, 0, MPI_COMM_WORLD);

    // 解包数据
    int position = 0;
    MPI_Unpack(buffer, total_size, &position, &rcvDict.codesNr, 1, MPI_INT, MPI_COMM_WORLD);
    rcvDict.codes = calloc(rcvDict.codesNr, sizeof(Code));

    for (int i = 0; i < rcvDict.codesNr; i++) {
        MPI_Unpack(buffer, total_size, &position, &rcvDict.codes[i].character, 1, MPI_CHAR, MPI_COMM_WORLD);
        MPI_Unpack(buffer, total_size, &position, &rcvDict.codes[i].length, 1, MPI_INT, MPI_COMM_WORLD);
        rcvDict.codes[i].code = calloc(rcvDict.codes[i].length, sizeof(char));
        MPI_Unpack(buffer, total_size, &position, rcvDict.codes[i].code, rcvDict.codes[i].length, MPI_CHAR, MPI_COMM_WORLD);
    }

    // 打印接收数据(逻辑不变)
    printf("\nreceived data\n");
    for (int i = 0; i < rcvDict.codesNr; i++) {
        printf("codes[%d]:\n\t", i);
        printf("character: %c\tlength: %d\tcode: ", rcvDict.codes[i].character, rcvDict.codes[i].length);
        for (int j = 0; j < rcvDict.codes[i].length; j++)
            printf("%c", rcvDict.codes[i].code[j]);
        printf("\n");
    }

    // 释放资源
    free(buffer);
    for (int i = 0; i < rcvDict.codesNr; i++)
        free(rcvDict.codes[i].code);
}

方案对比

  • 方案1:改动最小,适合希望快速简化代码的场景,保留分步传输的逻辑但去掉循环发送。
  • 方案2:最简洁,将多步通信合并为两步,减少通信开销,代码结构更清晰,适合数据量较大的场景。

内容的提问来源于stack exchange,提问作者Scotty

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 05:30:47