---
# System prepended metadata

title: Environment Management
tags: [MPI]

---

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