# 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 時的參考資訊