# Environment Management ## 環境控制 ### `MPI_Init(int *argc, char ***argv)` - **初始化** MPI 執行環境 - 包括一些定義於函式庫中的物件 - 呼叫此 function **前**,***不能*** 呼叫其他 MPI function, - 在一個 process 中 **只能呼叫一次** - 通常用 `argc` 和 `argv` 的指標當引數呼叫 ```c int main(int argc, char** argv) { MPI_Init(&argc, &argv); // ... } ``` - 也可以用 `NULL` 當引數呼叫 - > 但好像會有 memory leak 的問題,所以應該避免 ```c int main(int argc, char** argv) { MPI_Init(NULL, NULL); // ... } ``` ### `MPI_Finalize()` - **終止** MPI 執行環境 - 呼叫此 function **後**,***不能*** 呼叫其他 MPI function - 此 function 在一個 process 中 **只能呼叫一次** - 如果沒有呼叫 `MPI_Finalize()`,只要有其中 **一個 process 結束** 執行,**所有 processes 都會被強制終止** - 可能導致程式有非預期的執行結果 - 部分 process 可能執行到一半就被中斷 ```c #include <mpi.h> int main(int argc, char** argv) { // non-MPI function calls // ... MPI_Init(&argc, &argv); // MPI and non-MPI function calls // ... MPI_Finalize(); // non-MPI function calls // ... } ``` ### `MPI_Abort(MPI_Comm comm, int errorcode)` - **強制停止** 某 communicator 中的所有 process - 用在發生錯誤或例外 (exception),需要強制停止程式執行的時候 - 在大部份的 MPI 實作中,不管 `comm` 是什麼,都會停止所有 processes - `comm`: 要強制停止的 communicator - `errorcode`: 拋出的錯誤碼 ## 取得執行資訊 ### `MPI_Comm_rank(MPI_Comm comm, int *size)` - 取得目前 process、在某個 communicator 中的 **rank** - `comm`: 要查詢的 communicator - `rank`: 執行結果,指向用來儲存 rank 的記憶體空間 :::success ```c // demo.c #include <mpi.h> #include <stdio.h> int main(int argc, char** argv) { MPI_Init(&argc, &argv); int rank; MPI_Comm_rank(MPI_COMM_WORLD, &rank); printf("Rank %d: Hello, World!\n", rank); MPI_Finalize(); } ``` - **Compile & Execute** ```bash mpicc demo.c -o demo mpirun -np 2 demo ``` - **Output** ``` Rank 0: Hello, World! Rank 1: Hello, World! ``` > 實際順序可能不同 ::: ### `MPI_Comm_size(MPI_Comm comm, int *size)` - 取得某個 communicator 的 size (**有幾個 process**) - `comm`: 要查詢的 communicator - `size`: 執行結果,指向用來儲存 communicator size 的記憶體空間 :::success ```c // demo.c #include <mpi.h> #include <stdio.h> int main(int argc, char** argv) { MPI_Init(&argc, &argv); int size; MPI_Comm_size(MPI_COMM_WORLD, &size); printf("There are %d processes\n", size); MPI_Finalize(); } ``` - **Compile & Execute** ```bash mpicc demo.c -o demo mpirun -np 2 demo ``` - **Output** ``` There are 2 processes There are 2 processes ``` ::: ### `MPI_Get_processor_name(char *name, int *resultlen)` - 取得 processor 的名稱 - 會拿到執行此 processes 的 **node 的 hostname** - `name`: 指向存放 processor name 的 buffer - processor name 的最大長度是 `MPI_MAX_PROCESSOR_NAME` (包含 `'\0'`) - 所以 buffer 的大小至少是 `MPI_MAX_PROCESSOR_NAME` - 可以直接宣告長度為 `MPI_MAX_PROCESSOR_NAME` 的字元陣列當作 buffer - `resultlen`: 儲存實際的 processor name 的長度 - 不可為 `NULL` :::success ```c // demo.c #include <mpi.h> #include <stdio.h> int main(int argc, char** argv) { MPI_Init(&argc, &argv); int name_length; char node_name[MPI_MAX_PROCESSOR_NAME]; MPI_Get_processor_name(node_name, &name_length); printf("Node: %s\n", node_name); MPI_Finalize(); } ``` - **Compile & Execute** ```bash mpicc demo.c -o demo mpirun -np 1 demo ``` - **Output** ``` Node: <your_hostname> ``` > <your_hostname> 和你機器上實際的 hostmane 相同 ::: ## 計時 ### `MPI_Wtick()` - 回傳計時功能的 **精準度** - 代表可測量的最小時間間隔 - 以秒為單位,使用 `double` 格式儲存 (雙精度浮點數) ### `MPI_Wtime()` - 回傳目前的 elapsed wall clock time (從程式啟動到目前所經過的時間) - 以秒為單位,使用 `double` 格式儲存 (雙精度浮點數) - 可以在某個運算的前後各取一次時間,將兩個時間相減就是運算所需的時間 ```c double start = MPI_Wtick(); // some operations ... double end = MPI_Wtick(); printf("Time: %lf\n", end - start); ``` :::success ```c // demo.c #include <mpi.h> #include <stdio.h> int main(int argc, char** argv) { MPI_Init(&argc, &argv); double start = MPI_Wtime(); int limit = (1 << 16); for (int i = 0; i < limit; i++) { } double end = MPI_Wtime(); printf("Time: %lf\n", end - start); MPI_Finalize(); } ``` - **Compile & Execute** ```bash mpicc demo.c -o demo mpirun -np 1 demo ``` - **Output** ``` Time: 0.000577 ``` > 結果僅供參考,實際執行時間可能不同 ::: ## 應用範例 ### Branch - 用 `if...else...` 或其他條件語法,根據 `rank` 的不同、執行不同工作 - 讓某些程式碼只在特定 process 上執行 - 讓不同 process 執行不同的程式碼 - 常用在對外的輸入、輸出 - 讓對外的輸入、輸出只在某一個 process 上進行,方便和外界的溝通 :::info ```c // demo.c #include <mpi.h> #include <stdio.h> int main(int argc, char** argv) { int rank; MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &rank); // some operations ... // ... if (rank == 0) { // output // ... } MPI_Finalize(); } ``` ::: ### Static Load Balancing - 在開始運算前,讓每個 process 根據自己的 rank 和 communicator size,決定要進行哪些運算工作 - 用 communicator size 計算一個 process 要運算多少資料 - 用 rank 判斷自己要運算哪一段資料 - 優點 - 所有 process 都參與運算,不需要一個用來主控的 process - 工作分配時不需要進行溝通,減少傳輸 - 缺點 - 不好實作較複雜的工作分配演算法 - 沒辦法依照實際的運算進度、動態的分配工作 下面是兩種簡單的工作分配的方式 :::success **分段切割** - 把資料分割成多個連續的小區段 - 一個 process 處理一個區段 - **優點** - 邏輯簡單 - 如果運算結果和順序會有關係,最後用其他通訊 API 傳輸時比較簡單 - 資料整體順序不會改變 - **缺點** - 如果資料量不是 process 數量的倍數,分配可很不平均 - 不一定能平均分配工作量 - 只能確保每個 process 處理相同數量的資料 - 但是每個資料所需的運算量可能不同 ```c // demo.c #include <math.h> #include <mpi.h> #include <stdio.h> #define N 12 int main(int argc, char** argv) { int rank, comm_size; MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &comm_size); // 計算一個 process 要處理多少資料 int chunk_size = ceil((float)N / comm_size); // 計算資料的起始和終止位置 int start = chunk_size * rank; int end = start + chunk_size; for (int i = start; i < end; i++) { if (end < N) { // some operations ... // ... printf("Process %d: %d\n", rank, i); } } MPI_Finalize(); } ``` - 這邊假設有 `N` 筆資料要處理 - 程式會輸出每個 process 需要處理的資料的 index - **Compile & run** ``` mpicc demo.c -lm -o demo mpirun -np 4 demo ``` > 編譯要有 `-lm`,因為有使用 math.h - **Output** ``` Process 0: 0 Process 0: 1 Process 0: 2 Process 1: 3 Process 1: 4 Process 1: 5 Process 2: 6 Process 2: 7 Process 2: 8 Process 3: 9 Process 3: 10 Process 3: 11 ``` > 輸出結果僅供參考,每次執行的實際順序可能不同 ::: :::success **同餘切割** - 若有 m 個 process,每筆資料都有一個索引 i - 則每個 process 處理 i % m == rank 的資料 - **優點** - 看起來比較複雜,但實際上更好實作 - 負載更加平衡 - 資料量更平均 - 有時候運算量也更平均 - **缺點** - 如果執行結果和資料原始順序相關,彙整執行結果的時候比較麻煩 ```c // demo.c #include <mpi.h> #include <stdio.h> #define N 12 int main(int argc, char** argv) { int rank, comm_size; MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &comm_size); // i 從 0 開始 每次往上加一個 comm_size // 若 comm_size = 4 // i: 0, 4, 8, .... for (int i = 0; i < N; i += comm_size) { // 計算這次計算資料的 index // 當 i = 0, process0~3 分別處理 index 0~3 // 當 i = 4, process0~3 分別處理 index 4~7 int index = i + rank; if (index < N) { // some operations ... printf("Process %d: %d\n", rank, index); } } MPI_Finalize(); } ``` - **Compile & run** ``` mpicc demo.c -o demo mpirun -np 4 demo ``` - **Output** ``` Process 0: 0 Process 0: 4 Process 0: 8 Process 1: 1 Process 1: 5 Process 1: 9 Process 2: 2 Process 2: 6 Process 2: 10 Process 3: 3 Process 3: 7 Process 3: 11 ``` > 輸出結果僅供參考,每次執行的實際順序可能不同 ::: ### Debug 資訊 - Rank, processor name 可以當作 debug 時的參考資訊