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
相关产品推荐
相关产品推荐

