首頁 > 軟體

C++ Boost MPI介面詳細講解

2022-11-21 14:00:57

一、說明

Boost.MPI 提供了 MPI 標準(訊息傳遞介面)的介面。該標準簡化了並行執行任務的程式的開發。您可以使用執行緒或通過共用記憶體或網路連線使多個程序相互通訊來開發此類程式。 MPI 的優點是你不需要關心這些細節。您可以完全專注於並行化您的程式。

缺點是您需要 MPI 執行時環境。如果您控制執行時環境,MPI 只是一個選項。例如,如果你想分發一個可以通過雙擊啟動的程式,你將無法使用 MPI。雖然作業系統開箱即用地支援執行緒、共用記憶體和網路,但它們通常不提供 MPI 執行時環境。使用者需要執行額外的步驟來啟動 MPI 程式。

  • 開發和執行時環境
  • 簡單的資料交換
  • 非同步資料交換
  • 集體資料交換

二、開發和執行時環境

MPI 定義了用於平行計算的函數。平行計算是指在支援任務並行執行的執行時環境中可以並行執行任務的程式。這樣的執行時環境通常基於多個處理器。由於單個處理器只能順序執行程式碼,因此連結多個處理器會建立一個可以並行執行任務的執行時環境。如果連線了數千個處理器,結果就是一臺平行計算機——一種通常只在超級計算機中才能找到的架構。 MPI 來自於尋找更容易地為超級計算機程式設計的方法的搜尋。

如果你想使用 MPI,你需要一個標準的實現。雖然 MPI 定義了許多功能,但它們通常不受開箱即用的作業系統支援。例如,Windows 的桌面版本不附帶 MPI 支援。

最重要的 MPI 實現是 MPICH 和 Open MPI。 MPICH 是最早的 MPI 實現之一。它自 1990 年代中期就已存在。 MPICH 是一種成熟且可移植的實現,並得到積極維護和更新。 Open MPI 的第一個版本於 2005 年釋出。由於 Open MPI 是一項共同作業成果,其中包括許多負責早期 MPI 實現的開發人員,因此 Open MPI 被視為未來的標準。然而,這並不意味著可以忽略 MPICH。有幾種基於 MPICH 的 MPI 實現。例如,Microsoft 釋出了一個名為 Microsoft HPC Pack 的 MPI 實現,它基於 MPICH。

MPICH 為各種作業系統(如 Windows、Linux 和 OS X)提供安裝檔案。如果您需要 MPI 實現並且不想從原始碼構建它,MPICH 安裝檔案是開始使用 MPI 的最快途徑。

MPICH 安裝檔案包含開發 MPI 程式所需的標頭檔案和庫。此外,它們還包含一個 MPI 執行時環境。因為 MPI 程式同時在多個處理器上執行任務,所以它們在多個程序中執行。一個 MPI 程式會啟動多次,而不僅僅是一次。同一 MPI 程式的多個範例在多個處理器上執行,並通過 MPI 標準定義的函數進行通訊。

您無法通過雙擊啟動 MPI 程式。您使用一個幫助程式,通常稱為 mpiexec。您將 MPI 程式傳遞給 mpiexec,它會在 MPI 執行時環境中啟動您的程式。命令列選項確定啟動了多少個程序以及它們如何通訊——例如,通過通訊端或共用記憶體。因為 MPI 執行時環境會處理這些細節,所以您可以專注於並行程式設計。

如果您決定使用 MPICH 的安裝檔案,請注意 MPICH 僅提供 64 位版本。您必須使用 64 位編譯器通過 MPICH 開發 MPI 程式並構建 64 位版本的 Boost.MPI。

三、簡單資料交換

Boost.MPI 是 MPI 標準的 C++ 介面。該庫使用名稱空間 boost::mpi。包含標頭檔案 boost/mpi.hpp 就足以存取所有類和函數。

範例 47.1。 MPI 環境和通訊器

#include <boost/mpi.hpp>
#include <iostream>
int main(int argc, char *argv[])
{
  boost::mpi::environment env{argc, argv};
  boost::mpi::communicator world;
  std::cout << world.rank() << ", " << world.size() << 'n';
}

Example47.1

範例 47.1 是一個簡單的 MPI 程式。它使用兩個類,您將在隨後的所有範例中找到它們。 boost::mpi::environment 初始化 MPI。建構函式從 MPI 標準呼叫函數 MPI_Init()。解構函式呼叫 MPI_Finalize()。 boost::mpi::communicator 用於建立通訊器。通訊器是 MPI 的核心概念之一,支援程序之間的資料交換。

boost::mpi::environment 是一個非常簡單的類,只提供了幾個成員函數。您可以呼叫 initialized() 檢查 MPI 是否已成功初始化。成員函數返回一個 bool 型別的值。 processor_name() 以 std::string 的形式返回當前程序的名稱。 abort() 會停止 MPI 程式,而不僅僅是當前程序。您將一個 int 值傳遞給 abort()。該值將作為 MPI 程式的返回值傳遞到 MPI 執行時環境。對於大多數 MPI 程式,您不需要這些成員函數。您通常在程式開始時範例化 boost::mpi::environment,然後不使用該物件——如範例 47.1 和本章中的以下範例。

boost::mpi::communicator 更有趣。此類是一個通訊器,它連結作為 MPI 程式一部分的程序。每個程序都有一個等級,它是一個整數——所有程序都會被列舉。程序可以通過在通訊器上呼叫 rank() 來發現其等級。如果程序想知道有多少個程序,它會呼叫 size()。

要執行範例 47.1,您必須使用您正在使用的 MPI 實現提供的幫助程式。對於 MPICH,輔 助程式稱為 mpiexec。您可以通過以下命令使用此幫助程式執行範例 47.1:

mpiexec-n4sample.exe

mpiexec 需要一個 MPI 程式的名稱和一個告訴它要啟動多少程序的選項。選項 -n 4 告訴 mpiexec 啟動四個程序。因此 MPI 程式啟動了四次。但是,這四個過程並不是獨立的。它們通過 MPI 執行時環境連結,並且它們都屬於同一個通訊器,這給每個程序一個等級。如果您使用四個程序執行範例 47.1,rank() 返回一個從 0 到 3 的數位和 size() 4。

請注意,輸出可能會混淆。畢竟,有四個程序同時寫入標準輸出流。例如,不知道排名為 0 或任何其他排名的程序是否是第一個寫入標準輸出流的程序。也有可能一個程序在寫入標準輸出流時會中斷另一個程序。在另一個程序寫入標準輸出流之前,被中斷的程序可能無法完成寫入其等級和通訊器的大小,從而破壞輸出。

範例 47.2。傳送和接收資料的阻塞函數

#include <boost/mpi.hpp>
#include <iostream>
int main(int argc, char *argv[])
{
  boost::mpi::environment env{argc, argv};
  boost::mpi::communicator world;
  if (world.rank() == 0)
  {
    int i;
    world.recv(1, 16, i);
    std::cout << i << 'n';
  }
  else if (world.rank() == 1)
  {
    world.send(0, 16, 99);
  }
}

boost::mpi::communicator 提供了兩個簡單的成員函數,send() 和 recv(),用於在兩個程序之間交換資料。它們是阻塞函數,僅在傳送或接收資料時才返回。這對於 recv() 尤其重要。如果在沒有其他程序向其傳送資料的情況下呼叫 recv(),呼叫將阻塞並且程序將在呼叫中停止。

在範例 47.2 中,等級為 0 的程序使用 recv() 接收資料。等級為 1 的程序使用 send() 傳送資料。如果你用兩個以上的程序啟動程式,其他程序什麼都不做就直接退出。

您將三個引數傳遞給 send():第一個引數是資料應傳送到的程序的等級。第二個引數是用於識別資料的標籤。第三個引數是資料。

標籤始終是一個整數。在範例 47.2 中,標籤是 16。標籤可以識別對 send() 的呼叫。您會看到該標籤與 recv() 一起使用。

傳遞給 send() 的第三個引數是 99。這個數位從等級 1 的程序傳送到等級 0 的程序。Boost.MPI 支援所有原始型別。可以直接傳送像 99 這樣的 int 值。

recv() 需要類似的引數。第一個引數是應該從中接收資料的程序的等級。第二個引數是將對 recv() 的呼叫與對 send() 的呼叫連結起來的標籤。第三個引數是存放接收到的資料的變數。

如果您使用至少兩個程序執行範例 47.2,則會顯示 99。

範例 47.3。從任何程序接收資料

#include <boost/mpi.hpp>
#include <iostream>
int main(int argc, char *argv[])
{
  boost::mpi::environment env{argc, argv};
  boost::mpi::communicator world;
  if (world.rank() == 0)
  {
    int i;
    world.recv(boost::mpi::any_source, 16, i);
    std::cout << i << 'n';
  }
  else
  {
    world.send(0, 16, world.rank());
  }
}

Example47.3

範例 47.3 基於範例 47.2。它不傳送數位 99,而是傳送呼叫 send() 的程序的等級。這可以是等級大於 0 的任何程序。

對等級為 0 的程序的 recv() 呼叫也發生了變化。 boost::mpi::any_source 是第一個引數。這意味著對 recv() 的呼叫將接受來自任何傳送帶有標籤 16 的資料的程序的資料。

如果您使用兩個程序啟動範例 47.3,將顯示 1。畢竟,只有一個程序可以呼叫 send()——rank 為 1 的程序。如果你啟動程式時有兩個以上的程序,不知道會顯示哪個數位。在這種情況下,多個程序將呼叫 send() 並嘗試傳送它們的排名。哪個程序最先,因此顯示哪個排名是隨機的。

範例 47.4。使用 boost::mpi::status 檢測發件人

#include <boost/mpi.hpp>
#include <iostream>
int main(int argc, char *argv[])
{
  boost::mpi::environment env{argc, argv};
  boost::mpi::communicator world;
  if (world.rank() == 0)
  {
    int i;
    boost::mpi::status s = world.recv(boost::mpi::any_source, 16, i);
    std::cout << s.source() << ": " << i << 'n';
  }
  else
  {
    world.send(0, 16, 99);
  }
}

recv() 的返回值型別為 boost::mpi::status。此類提供了一個成員函數 source(),它返回從中接收資料的程序的等級。範例 47.4 告訴您數位 99 是從哪個程序收到的。

到目前為止,所有範例都使用 send() 和 recv() 來傳輸 int 值。在範例 47.5 中,傳輸了一個字串。

範例 47.5。使用 send() 和 recv() 傳輸陣列

#include <boost/mpi.hpp>
#include <iostream>
int main(int argc, char *argv[])
{
  boost::mpi::environment env{argc, argv};
  boost::mpi::communicator world;
  if (world.rank() == 0)
  {
    char buffer[14];
    world.recv(boost::mpi::any_source, 16, buffer, 13);
    buffer[13] = '';
    std::cout << buffer << 'n';
  }
  else
  {
    const char *c = "Hello, world!";
    world.send(0, 16, c, 13);
  }
}

send() 和 recv() 可以傳輸陣列和單個值。範例 47.5 傳輸字元陣列中的字串。因為 send() 和 recv() 支援像 char 這樣的基本型別,所以可以毫無問題地傳輸 char 陣列。

send() 將指向字串的指標作為其第三個引數,並將字串的大小作為其第四個引數。傳遞給 recv() 的第三個引數是指向儲存接收資料的陣列的指標。第四個引數告訴 recv() 應該接收多少個字元並將其儲存在緩衝區中。範例 47.5 寫出 Hello, world!到標準輸出流。

範例 47.6。使用 send() 和 recv() 傳輸字串

#include <boost/mpi.hpp>
#include <boost/serialization/string.hpp>
#include <string>
#include <iostream>
int main(int argc, char *argv[])
{
  boost::mpi::environment env{argc, argv};
  boost::mpi::communicator world;
  if (world.rank() == 0)
  {
    std::string s;
    world.recv(boost::mpi::any_source, 16, s);
    std::cout << s << 'n';
  }
  else
  {
    std::string s = "Hello, world!";
    world.send(0, 16, s);
  }
}

儘管 Boost.MPI 只支援原始型別,但這並不意味著它不能傳輸非原始型別的物件。 Boost.MPI 與 Boost.Serialization 一起工作。可以根據 Boost.Serialization 規則序列化的物件可以使用 Boost.MPI 進行傳輸。

範例 47.6 傳輸“Hello, world!”這次傳輸的值不是字元陣列,而是 std::string。 Boost.Serialization 提供標頭檔案 boost/serialization/string.hpp,只需要包含它以使 std::string 可序列化。

到此這篇關於C++ Boost MPI介面詳細講解的文章就介紹到這了,更多相關C++ Boost MPI內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com