一句話先懂:MPI(Message Passing Interface) 是「讓很多獨立的程式(通常在不同電腦上)彼此傳訊息、一起把大事拆開來做」的標準。最常見在高效能運算(HPC):模擬、科學計算、金融風險、影像處理、AI 前/後處理等。
心法:MPI 不是執行緒(Thread),也不是共用記憶體。它是**很多個獨立的「小程式副本」**在一起跑,每個都有自己的記憶體,只靠「寄信」溝通。
MPI_COMM_WORLD
(所有人都在的群組)。小圖:
MPI_COMM_WORLD(群組)
├─ rank 0
├─ rank 1
├─ rank 2
└─ rank 3
Linux / WSL2 / macOS(Homebrew):裝 Open MPI 或 MPICH 其中一種即可。
sudo apt install mpich
(或 openmpi-bin libopenmpi-dev
)brew install open-mpi
檢查:mpicc -v
、mpirun --version
怎麼跑:
mpicc hello.c -o hello
mpirun -np 4 ./hello
(-np 4
代表 4 個 rank)Windows 可用 WSL2 跑 Linux 版本最省事。
#include <stdio.h>
#include <mpi.h>
int main(int argc, char** argv){
MPI_Init(&argc, &argv); // 啟動 MPI
int world_size, world_rank;
MPI_Comm_size(MPI_COMM_WORLD, &world_size); // 總共有幾個人
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank); // 我是第幾號
printf("Hello from rank %d of %d\n", world_rank, world_size);
MPI_Finalize(); // 關閉 MPI
return 0;
}
執行:mpirun -np 4 ./hello
輸出(順序可能不同):
Hello from rank 2 of 4
Hello from rank 0 of 4
Hello from rank 3 of 4
Hello from rank 1 of 4
# hello.py
from mpi4py import MPI
comm = MPI.COMM_WORLD
print(f"Hello from rank {comm.Get_rank()} of {comm.Get_size()}")
執行:mpirun -np 4 python hello.py
MPI_Send / MPI_Recv
想像寄信:要寫「收件人 rank、內容大小、資料型別、標籤 tag」。
if (world_rank == 0) {
int num = 42;
MPI_Send(&num, 1, MPI_INT, /*dest=*/1, /*tag=*/99, MPI_COMM_WORLD);
} else if (world_rank == 1) {
int num;
MPI_Recv(&num, 1, MPI_INT, /*source=*/0, /*tag=*/99, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
printf("Rank 1 got %d from rank 0\n", num);
}
避免死鎖的小訣竅
兩邊同時 MPI_Send
等對方 MPI_Recv
可能卡住(取決於實作和訊息大小)。
安全做法:一邊先收,一邊先送;或用 非阻塞(下一節)。
MPI_Isend / MPI_Irecv
好處:送/收立刻返回,你可以一邊算、一邊等傳輸,最後用 MPI_Wait/MPI_Waitall
確認完成,達到重疊。
MPI_Request reqs[2];
MPI_Irecv(buf_in, n, MPI_FLOAT, left, 0, MPI_COMM_WORLD, &reqs[0]);
MPI_Isend(buf_out, n, MPI_FLOAT, right, 0, MPI_COMM_WORLD, &reqs[1]);
// 這段時間可以做別的計算(重疊通信與計算)
MPI_Waitall(2, reqs, MPI_STATUSES_IGNORE);
關鍵:貼好郵票就去忙別的,等郵差投遞完再確認收件。
MPI_Bcast
MPI_Scatter
、MPI_Gather
sum/max/min
等聚合;Allreduce
結果分給每個人
int local = world_rank + 1; // 例如每人各有一個數字
int global_sum = 0;
MPI_Allreduce(&local, &global_sum, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
if (world_rank == 0) {
printf("Global sum = %d\n", global_sum);
}
把 N
筆資料分成 P
份(P = world_size
),第 r
份的起訖:
int chunk = N / P;
int rem = N % P;
int start = r * chunk + (r < rem ? r : rem);
int count = chunk + (r < rem ? 1 : 0);
int end = start + count; // [start, end)
這樣能把多出來的幾筆平均分給前面的 rank,負載較平衡。
把 rank 排成 2D(MPI_Cart_create
),按方塊切;或簡單先切成「橫條」或「直條」。
2D 鄰居交換可用 Neighborhood Collectives(進階)或手工 Send/Recv
。
MPI_Wtime
double t0 = MPI_Wtime();
// ... 核心計算 ...
double t1 = MPI_Wtime();
if (world_rank == 0) printf("Step took %.3f ms\n", (t1 - t0)*1000);
速度提升(Speedup):單機時間 ÷ 多機時間
效率(Efficiency):Speedup ÷ 使用節點數(或 rank 數)
mpirun -np 8 -host node1,node2 ./app
srun -n 8 ./app
(常見於校園/雲端 HPC)狀況 | 可能原因 | 快修 |
---|---|---|
程式卡住(死鎖) | 雙方都 Send 等對方收 |
改一邊先 Recv ;或用 Isend/Irecv + Wait |
收到垃圾數 | Recv 的型別/數量/標籤不符 |
確認 count/type/tag 完全一致 |
結果不一致 | 少做同步、資料沒對齊 | 在關鍵點 MPI_Barrier (除錯用)、檢查每人資料範圍 |
集體通訊當機 | 參與人數不一致 | 所有 rank 都要呼叫同一個 collective |
太慢 | 訊息太碎、頻繁同步 | 合併訊息、用非阻塞、減少 collective 次數 |
本機跑得動、多機出錯 | 檔案路徑/權限/環境變數 | 統一工作目錄;用絕對路徑;在每台機器測 mpirun hostname |
Isend/Irecv
+ 計算 → Wait
,重疊通信與計算。MPI_Barrier
只在除錯或必要時使用。Allreduce
這類昂貴操作放在必要的迴圈外或降低頻率。MPI_Cart_create
/鄰居 collective 更合適。output_rank%03d.bin
)通常比大家同寫一檔快;或學 MPI-IO。MPI_COMM_WORLD
最常用)MPI_INT/MPI_FLOAT/...
,要和你的資料型別一致MPI = 很多獨立程式用傳訊息協作的標準。
先學:Init/Finalize
、Comm_rank/size
、Send/Recv
、Bcast/Allreduce
、Wtime
。
用正確的資料分割 + 避免死鎖 + 非阻塞重疊通信與計算,你就能把單機程式升級成可擴充到多台機器的 HPC 版本。需要時再結合 GPU,威力更上一層樓。