MPI Broadcast and Collective Communication

以下內容翻譯自:MPI Broadcast and Collective Communication

到目前爲止,在MPI教程中,我們已經審視了兩個進程之間的點對點通信。本課是集合通信部分的開始。集合通信是一種所有通信進程都參與其中的通信方式。在本課中,我們將討論集合通信的含義,並討論標準集體例程——廣播。

注——本網站的所有代碼均位於GitHub上。本教程的代碼位於tutorials/mpi-broadcast-and-collective-communication/code下。

集合通信和同步點

集合通信需要記住的一點是,它在進程間設置同步點。這意味着所有進程在它們全部開始執行之前必須到達其代碼中的某個點。

在詳細討論合通信例程之前,讓我們更詳細地研究同步。事實證明,MPI有一個專門用於同步進程的特殊函數:

MPI_Barrier(MPI_Comm communicator)

該函數的名稱非常具有描述力——該函數產生一個障礙,並且通信器中的任何進程都不能通過該障礙,直到所有這些進程調用該函數爲止。這是一個例子。想象一下水平軸表示程序的執行,圓圈表示不同的過程:

MPI_Barrier

進程0首先在第一次快照(T 1)時調用MPI_Barrier。當進程0在屏障上掛起時,進程1和進程3最終到達(T 2)。當進程2最後進入屏障(T 3)時,所有進程再次開始執行(T 4)。

MPI_Barrier可以用於很多事情。主要用途之一是同步一個程序,以便並行代碼部分可以準確計時。

想知道MPI_Barrier是如何實現的?當然你可以 :-) 你還記得發送和接收教程中的環形程序嗎?爲了讓你回憶起來,我們編寫了一個程序,以環形方式在所有進程中傳遞令牌。 這種類型的程序是實現屏障的最簡單方法之一,因爲在所有進程一起工作之前,令牌無法完成傳遞。這種類型的程序是實現一個屏障最簡單的方法之一,因爲在所有進程協同工作之前,令牌不能完成傳遞。

關於同步的最後一點注意事項——請始終記住您所做的每個集合調用都進行了同步。換句話說,如果您無法成功完成MPI_Barrier,那麼您也無法成功完成任何集合調用。如果您嘗試調用MPI_Barrier或其他集合例程而未確保通信器中的所有進程也會調用它,則您的程序將空閒。這可能會讓初學者感到困惑,所以要小心!

使用MPI_Bcast進行廣播

廣播是標準的集合通信技術之一。在廣播過程中,一個進程向通信器中的所有進程發送相同的數據。廣播的主要用途之一是將用戶輸入發送到並行程序,或向所有進程發送配置參數。

廣播的通信模式如下所示:

broadcast_pattern

在這個例子中,進程0是根進程,它有數據的初始副本。所有其他進程都會收到數據副本。

在MPI中,可以使用MPI_Bcast完成廣播。函數原型如下所示:

MPI_Bcast(
    void* data,
    int count,
    MPI_Datatype datatype,
    int root,
    MPI_Comm communicator)

儘管根進程和接收者進程執行不同的工作,但它們都調用相同的MPI_Bcast函數。當根進程(在我們的例子中,它是進程0)調用MPI_Bcast時,data變量將被髮送到所有其他進程。當所有接收者進程調用MPI_Bcast時,data變量將被來自根進程的數據填充。

使用MPI_SendMPI_Recv進行廣播

起初,似乎MPI_Bcast只是MPI_SendMPI_Recv的簡單封裝。事實上,我們現在可以製作這個功能封裝。我們的函數my_bcast位於bcast.c中。它採用與MPI_Bcast相同的參數,如下所示:

void my_bcast(void* data, int count, MPI_Datatype datatype, int root,
              MPI_Comm communicator) {
  int world_rank;
  MPI_Comm_rank(communicator, &world_rank);
  int world_size;
  MPI_Comm_size(communicator, &world_size);

  if (world_rank == root) {
    // If we are the root process, send our data to everyone
    int i;
    for (i = 0; i < world_size; i++) {
      if (i != world_rank) {
        MPI_Send(data, count, datatype, i, 0, communicator);
      }
    }
  } else {
    // If we are a receiver process, receive the data from the root
    MPI_Recv(data, count, datatype, root, 0, communicator,
             MPI_STATUS_IGNORE);
  }
}

根進程將數據發送給其他進程,而其他進程則從根進程接收數據。很簡單,對吧?如果您從repo的tutorials目錄運行my_bcast程序,則輸出應該與此類似。

>>> cd tutorials
>>> ./run.py my_bcast
mpirun -n 4 ./my_bcast
Process 0 broadcasting data 100
Process 2 received data 100 from root process
Process 3 received data 100 from root process
Process 1 received data 100 from root process

相信與否,我們的功能效率實際上非常低下!想象一下,每個進程只有一個輸出/輸入網絡連接。我們的函數僅使用一個來自進程0的網絡連接來發送所有數據。更智能的實現是基於樹的通信算法,可以一次使用更多可用的網絡鏈接。例如:

broadcast_tree

在上圖中,進程0開始時將數據發送給進程1。與我們前面的例子類似,在第二階段進程0也向進程2發送數據。這個例子的不同之處在於,進程1現在幫助完成根進程將數據轉發到進程3。在第二階段,一次使用兩個網絡連接。在樹形通信的每個後續階段,網絡利用率都會翻倍,直到所有進程都收到數據。

你認爲你可以編碼嗎?編寫這段代碼有點超出了本課的目的。如果你感覺很勇敢,那麼《Parallel Programming with MPI》就是一本很好的書,它帶有上述問題的完整代碼例子。

MPI_BcastMPI_SendMPI_Recv的比較

MPI_Bcast實現利用類似的樹廣播算法來實現良好的網絡利用率。我們的廣播功能相比MPI_Bcast如何?我們可以運行compare_bcast,這是一個課程代碼(compare_bcast.c)中包含的示例程序。在查看代碼之前,我們先來看一下MPI的一個定時功能——MPI_WtimeMPI_Wtime不接受任何參數,它只是返回自過去某一時間以來的一個浮點數。與C的time函數類似,您可以在整個程序中調用多個MPI_Wtime函數,並減去它們之間的差以獲取代碼段的時間。

我們來看看比較my_bcastMPI_Bcast的代碼。

for (i = 0; i < num_trials; i++) {
  // Time my_bcast
  // Synchronize before starting timing
  MPI_Barrier(MPI_COMM_WORLD);
  total_my_bcast_time -= MPI_Wtime();
  my_bcast(data, num_elements, MPI_INT, 0, MPI_COMM_WORLD);
  // Synchronize again before obtaining final time
  MPI_Barrier(MPI_COMM_WORLD);
  total_my_bcast_time += MPI_Wtime();

  // Time MPI_Bcast
  MPI_Barrier(MPI_COMM_WORLD);
  total_mpi_bcast_time -= MPI_Wtime();
  MPI_Bcast(data, num_elements, MPI_INT, 0, MPI_COMM_WORLD);
  MPI_Barrier(MPI_COMM_WORLD);
  total_mpi_bcast_time += MPI_Wtime();
}

在這個代碼中,num_trials是一個變量,說明應該執行多少個時間實驗。我們通過兩個不同的變量跟蹤兩個函數的累計時間。在程序結束時打印平均時間。要查看完整的代碼,請查看課程代碼中的compare_bcast.c

如果您從repo的tutorials目錄運行compare_bcast程序,則輸出應與此類似。

>>> cd tutorials
>>> ./run.py compare_bcast
/home/kendall/bin/mpirun -n 16 -machinefile hosts ./compare_bcast 100000 10
Data size = 400000, Trials = 10
Avg my_bcast time = 0.510873
Avg MPI_Bcast time = 0.126835

運行腳本使用16個處理器執行代碼,每個廣播執行100,000個整數,並進行10次測量時間。正如你所看到的,我使用通過以太網連接的16個處理器的實驗顯示,我們的簡單實現和MPI的實現之間存在顯着的時間差異。這裏是不同規模的時間結果。

Processors my_bcast MPI_Bcast
2 0.0344 0.0344
4 0.1025 0.0817
8 0.2385 0.1084
16 0.5109 0.1296

正如你所看到的,在兩個處理器上兩個實現沒有區別。這是因爲在使用兩個處理器時,MPI_Bcast的樹實現不提供任何額外的網絡利用率。但是,即使只有16個處理器,差異也可以清楚地觀察到。

嘗試自己運行代碼並在更大規模上進行實驗!

結論/接下來

有點理解集合程序了?在接下來的MPI教程中,我會介紹其他重要的集合通信程序 ——gathering 和 scattering

對於所有課程,請轉到MPI教程頁面。

想貢獻?

這個網站完全託管在 GitHub上。本網站不再由原作者(Wes Kendall)積極貢獻,但它放在GitHub上,希望其他人可以編寫高質量的MPI教程。點擊這裏獲取更多關於如何貢獻的信息。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章