1. 程式人生 > >MPI之聚合通訊-Scatter,Gather,Allgather

MPI之聚合通訊-Scatter,Gather,Allgather

一、 MPI_Scatter

MPI_Scatter與MPI_Bcast非常相似,都是一對多的通訊方式,不同的是後者的0號程序將相同的資訊傳送給所有的程序,而前者則是將一段array 的不同部分發送給所有的程序,其區別可以用下圖概括:這裡寫圖片描述

0號程序分發資料的時候是根據程序的編號進行的,array中的第一個元素髮送給0號程序,第二個元素則傳送給1號程序,以此類推。

MPI_Scatter(
    void* send_data,//儲存在0號程序的資料,array
    int send_count,//具體需要給每個程序傳送的資料的個數
    //如果send_count為1,那麼每個程序接收1個數據;如果為2,那麼每個程序接收2個數據
    MPI_Datatype send_datatype,//傳送資料的型別
    void* recv_data,//接收快取,快取 recv_count個數據
    int recv_count,
    MPI_Datatype recv_datatype,
    int root,//root程序的編號
    MPI_Comm communicator)

通常send_count等於array的元素個數除以程序個數。

二、 MPI_Gather MPI_Gather和MPI_scatter剛好相反,他的作用是從所有的程序中將每個程序的資料集中到根程序中,同樣根據程序的編號對array元素排序,如圖所示:這裡寫圖片描述 其函式為

MPI_Gather(
    void* send_data,
    int send_count,
    MPI_Datatype send_datatype,
    void* recv_data,
    int recv_count,//注意該引數表示的是從單個程序接收的資料個數,不是總數
    MPI_Datatype recv_datatype,
    int root,
    MPI_Comm communicator)

三、MPI_Allgather 當資料分佈在所有的程序中時,MPI_Allgather將所有的資料聚合到每個程序中。這裡寫圖片描述

MPI_Allgather(
    void* send_data,
    int send_count,
    MPI_Datatype send_datatype,
    void* recv_data,
    int recv_count,
    MPI_Datatype recv_datatype,
    MPI_Comm communicator)

四、例項 問題描述: 我們的函式需要在每個程序中取一個數字,並返回其所有流程中所有其他數字的相關排名。 與此同時,我們將需要其他雜項資訊,例如正在使用的通訊器以及正在排名的數字的資料型別。 整體函式表示:

TMPI_Rank(
    void *send_data,
    void *recv_data,
    MPI_Datatype datatype,
    MPI_Comm comm)

TMPI_Rank接收一個包含一個datatype型別的send_data緩衝區。 recv_data在包含send_data的rank值的每個程序上只收到一個整數。 comm變數是正在進行排名的通訊器。

解決並行排序問題的第一步是排序所有程序的所有數字。 這必須完成,以便我們可以在整個數字集中找到每個數字的排名。 有很多方法可以做到這一點。 最簡單的方法是將所有數字收集到一個程序並對數字進行排序。

void *gather_numbers_to_root(void *number, MPI_Datatype datatype,
                             MPI_Comm comm) {
  int comm_rank, comm_size;
  MPI_Comm_rank(comm, &comm_rank);
  MPI_Comm_size(comm, &comm_size);

  // 根據使用的資料型別,給根程序分配size
  int datatype_size;
  MPI_Type_size(datatype, &datatype_size);
  void *gathered_numbers;
  if (comm_rank == 0) {
    gathered_numbers = malloc(datatype_size * comm_size);
  }

  // 收集根程序的所有數字
  MPI_Gather(number, 1, datatype, gathered_numbers, 1,
             datatype, 0, comm);

  return gathered_numbers;
}

根程序必須在此函式中收集comm_size數字,所以它malloc一個datatype_size * comm_size長度的陣列。在使用MPI_Gather在根程序上收集數字之後,數字必須在根程序中進行排序,以便可以確定其編號。

先定義一個結構體

typedef struct {
  int comm_rank;
  union {
    float f;
    int i;
  } number;
} CommRankNumber;

排序使用C標準庫函式:

int *get_ranks(void *gathered_numbers, int gathered_number_count,
               MPI_Datatype datatype) {
  int datatype_size;
  MPI_Type_size(datatype, &datatype_size);

  //將收集的數字陣列轉換為CommRankNumbers陣列。
  // 這使我們能夠對數字進行排序,並保留擁有數字的程序資訊。
  CommRankNumber *comm_rank_numbers = malloc(
    gathered_number_count * sizeof(CommRankNumber));
  int i;
  for (i = 0; i < gathered_number_count; i++) {
    comm_rank_numbers[i].comm_rank = i;
    memcpy(&(comm_rank_numbers[i].number),
           gathered_numbers + (i * datatype_size),
           datatype_size);
  }

  // 根據資料型別進行排序
  if (datatype == MPI_FLOAT) {
    qsort(comm_rank_numbers, gathered_number_count,
          sizeof(CommRankNumber), &compare_float_comm_rank_number);
  } else {
    qsort(comm_rank_numbers, gathered_number_count,
          sizeof(CommRankNumber), &compare_int_comm_rank_number);
  }

  // comm_rank_numbers被排序,為每個程序建立一個編號陣列。 該陣列的第i個元素包含程序i傳送的數字的編號。數字排序後,我們必須以正確的順序建立一個排列陣列,以便它們可以scatter回請求程序。
  int *ranks = (int *)malloc(sizeof(int) * gathered_number_count);
  for (i = 0; i < gathered_number_count; i++) {
    ranks[comm_rank_numbers[i].comm_rank] = i;
  }

  // Clean up and return the rank array
  free(comm_rank_numbers);
  return ranks;
}

綜合可得:

int TMPI_Rank(void *send_data, void *recv_data, MPI_Datatype datatype,
             MPI_Comm comm) {
  // 首先檢查基本情況 - 僅支援此函式的MPI_INT和MPI_FLOAT。
  if (datatype != MPI_INT && datatype != MPI_FLOAT) {
    return MPI_ERR_TYPE;
  }

  int comm_size, comm_rank;
  MPI_Comm_size(comm, &comm_size);
  MPI_Comm_rank(comm, &comm_rank);

  // 要計算編號,我們必須將數字收集到一個程序中,對數字進行排序,然後分散結果的等級值。 
  //首先收集comm的程序0的數字。
  void *gathered_numbers = gather_numbers_to_root(send_data, datatype,
                                                  comm);

  // 獲得每個程序的編號
  int *ranks = NULL;
  if (comm_rank == 0) {
    ranks = get_ranks(gathered_numbers, comm_size, datatype);
  }

  // Scatter the rank results
  MPI_Scatter(ranks, 1, MPI_INT, recv_data, 1, MPI_INT, 0, comm);

  // Do clean up
  if (comm_rank == 0) {
    free(gathered_numbers);
    free(ranks);
  }
}

流程如下:

這裡寫圖片描述 五、總結 本節介紹了三種聚合通訊,分別對應一對多,多對一,多對多通訊。