【问题标题】:Gathering strings with MPI_Gather openmpi c使用 MPI_Gather openmpi c 收集字符串
【发布时间】:2017-07-03 12:20:40
【问题描述】:

我想为每个进程生成一个字符串,然后收集所有内容。但是每个进程中创建的字符串是通过附加int和chars来创建的。

我仍然无法正确收集所有内容。我可以一一打印所有部分字符串,但如果我尝试打印 rcv_string,我只会得到一个部分字符串,或者可能是分段错误。

我尝试使用 memset 在字符串末尾添加零,动态和静态地为字符串保留内存,......但我没有找到方法。

如果有人知道如何初始化字符串并正确收集以实现目标,那就太好了。

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

    int rank;
    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    char *string;        // ????????????
    char *rcv_string;    // ????????????

    if (rank == 0)  {
        sprintf(string+strlen(string), "%dr%dg%db%dl\n",255,255,255,0);
    }
    else if (rank == 1) {
        sprintf(string+strlen(string), "%dr%dg%db%dl\n",255,255,255,0);
    }
    else if (rank == 2) {
        sprintf(string+strlen(string), "%dr%dg%db%dl\n",255,255,255,0);
    }
    else if (rank == 3) {
        sprintf(string+strlen(string), "%dr%dg%db%dl\n",255,255,255,0);
    }
    else if (rank == 4) {
        sprintf(string+strlen(string), "%dr%dg%db%dl\n",255,255,255,0);
    }
    else if (rank == 5) {
        sprintf(string+strlen(string), "%dr%dg%db%dl\n",255,255,255,0);
    }

    MPI_Gather(string,???,MPI_CHAR,rcv_string,???,MPI_CHAR,0,MPI_COMM_WORLD);

    if (rank == 0) {
        printf("%s",rcv_string);
    }

    MPI_Finalize();
    return 0;
}

【问题讨论】:

  • 为了避免 XY 问题:收集实际数据(例如 {255,255,255,0})而不是 C 字符串通常更直接。您的应用程序中是否有任何东西从根本上要求您传达 C 字符串而不是底层数据?

标签: c mpi openmpi


【解决方案1】:

尝试以下方法:

#define MAX_STR_LEN 100

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

    int rank, size;
    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &size);

    char string[MAX_STR_LEN] = "some string";

    char *rcv_string = NULL;
    if (rank == 0) {
        // Only the master needs to allocate the memory
        // for the result string which needs to be large
        // enough to contain the input strings from `size`
        // peers.
        rcv_string = malloc(MAX_STR_LEN * size);
    }

    ...same code...

    MPI_Gather(string, strlen(string), MPI_CHAR,
               rcv_string, MAX_STR_LEN, MPI_CHAR, 0, MPI_COMM_WORLD);

    if (rank == 0) {
        printf("%s",rcv_string);
        free(rcv_string);
    }

    MPI_Finalize();
    return 0;
}

使用mpirun -n 5 ./a.out 运行此代码会产生以下结果:

some string255r255g255b0l
some string255r255g255b0l
some string255r255g255b0l
some string255r255g255b0l
some string255r255g255b0l

确保定义MAX_STR_LEN,使其足够大以满足您的要求。如果值变大,您可能需要考虑堆分配(即malloc)。

【讨论】:

  • 这不起作用。将MAX_STR_LEN 用作recvcountMPI_Gather 将使所有字符串以MAX_STR_LEN 开头,中间有未初始化的值。此外,rcv_string 在聚集后未正确终止为空。
【解决方案2】:

我设法重现了只打印一个部分字符串的错误行为。

和你使用sprintf有关。

C 如何处理char 数组?

在 C 中使用数组时,您必须首先为其分配内存。动态或静态,没关系。假设您为 10 chars 分配了足够的内存。

char my_string[10];

不初始化,包含无意义的字符。

让我们假设my_string 包含"qwertyuiop"

假设您想用字符串foo 填充my_string。你使用sprintf

sprintf(my_string, "foo");

C如何用3个字符填充10个槽?

它用 3 个字符填充前 3 个插槽。然后,它用“字符串结尾”字符填充第 4 个插槽。这由'\0' 表示,在通过编译器时会转换为“字符串结尾”字符。

所以,在你的命令之后,my_string 包含"foo\0tyuiop"。如果打印出my_string,C就知道不打印出\0后面的废话了。

这与MPI_Gather 有何关系?

MPI_Gather收集不同进程的数组,并将它们全部放入一个数组中的一个进程。

如果您在进程 0 上拥有 "foo\0tyuiop",在进程 1 上拥有 "bar\0ghjkl;",它们将合并为 "foo\0tyuiopbar\0ghjkl;"

如您所见,来自进程 1 的数组出现在来自进程 0 的“行尾”字符之后。C 会将来自进程 1 的所有字符视为无意义的。

一个不完整的解决方案

与其尝试一次打印所有rcv_string,不如承认“字符串结尾”字符散布在各处。然后,根据它来自的过程,打印出具有不同“字符串开始”位置的字符串。

#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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

  int rank, size;
  MPI_Init(&argc, &argv);
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);
  MPI_Comm_size(MPI_COMM_WORLD, &size);

  int part_str_len = 18;

  char *my_string;
  char *rcv_string;

  if ((my_string = malloc(part_str_len*sizeof(char))) == NULL){
    MPI_Abort(MPI_COMM_WORLD,1);
  }
  if ((rcv_string = malloc(part_str_len*size*sizeof(char))) == NULL){
    MPI_Abort(MPI_COMM_WORLD,1);
  }

  sprintf(my_string, "%dr%dg%db%dl\n",255,255,255,0);

  MPI_Gather(my_string,18,MPI_CHAR,rcv_string,18,MPI_CHAR,0,MPI_COMM_WORLD);

  if (rank == 0) {
    printf("%s",rcv_string);
  }

  char *cat_string;
  if ((cat_string = malloc(part_str_len*size*sizeof(char))) == NULL){
    MPI_Abort(MPI_COMM_WORLD,1);
  }

  if (rank == 0){
    int i;
    sprintf(cat_string, "%s", rcv_string);
    for (i = 1; i < size; i++){
      strcat(cat_string, &rcv_string[part_str_len*i]);
    }
  }

  if (rank == 0) {
    printf("%s",cat_string);
  }

  free(my_string);
  free(rcv_string);
  free(cat_string);

  MPI_Finalize();
  return 0;
}

【讨论】:

  • 解释很好,但建议的解决方案通过使用硬编码大小和sprintf / strcat 而不是snprintf / strncat 树立了一个坏例子。这段代码可能会按原样工作,但一旦发生小事改变就会非常糟糕。另外:don't cast the result of malloc,使用MPI_Abort 而不是abort
  • 你的回答很好,我理解这个问题,所以我给你答案。但是现在我发现了另一个问题......附加到字符串的整数并不总是255(可能是1、20,...),所以字符串的长度可以不同。当我们为其保留内存时,这是一个问题,因为我无法精确地执行 strcat,因为我需要知道确切的长度。正如祖兰所说,我认为解决方案是通过使用 snprintf/strncat 来实现的。当我发布代码时,我没有意识到这一点,这是我的错误。我现在时间不多了,有空的时候再开个帖子。还是谢谢!
  • 这个想法是保留内存,例如每个进程20个字符,但我只能用进程0写5个,用进程1写12个,用进程3写19个......然后做一个收集(也许使用gatherv更好)并使用根进程收集并附加所有内容。谢谢!
  • 哦,是的。解决方案相当复杂。这是一个几乎不能让事情正常工作的补丁。我将编辑解决方案以解决演员表和MPI_Abort
猜你喜欢
  • 2020-09-09
  • 2016-10-23
  • 1970-01-01
  • 2021-12-08
  • 2015-10-31
  • 2022-08-20
  • 1970-01-01
  • 2020-08-09
  • 1970-01-01
相关资源
最近更新 更多