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


轉自:https://blog.csdn.net/sinat_22336563/article/details/70229243

參考:http://mpitutorial.com/tutorials/performing-parallel-rank-with-mpi/

一、 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);
  }
}

 

 

流程如下: 
這里寫圖片描述 
五、總結 
本節介紹了三種聚合通信,分別對應一對多,多對一,多對多通信。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM