MPI 特別講義まとめ
これは何か
2026-07-11 の特別講義(MPI 入門)の記録です。当日の殴り書きメモを、後から読み返せる形に整理しました。
内容は MPI の仕様(specification)レベルの基礎知識が中心です。特記がない限り、ここに書いたことは MPI 標準として定まっている情報です。実装(Open MPI など)に依存する挙動は、その旨を明記します。
- MPI(Message Passing Interface): 分散メモリ並列プログラミングの API を定めた国際標準仕様。
1. 並列計算と分散計算
| 用語 | 意味 |
|---|---|
| Parallel Computing | 1 つのタスクを小さいタスクに分割して同時に処理する |
| Distributed Computing | 複数の計算機を使って 1 つの問題を解く。多数のノードが互いに接続し、同じ問題を協調して解く |
メモリモデルによる分類
| モデル | 代表的な技術 | 特徴 |
|---|---|---|
| Shared Memory(共有メモリ) | pthreads、OpenMP、SHMEM | 同一メモリ空間を複数スレッドで共有 |
| Distributed Memory(分散メモリ) | MPI | プロセスごとに独立したメモリ。通信でデータをやり取り |
| Data Parallel(データ並列) | PGAS | データを分散配置し、グローバルアドレス空間として扱う |
補足:理論値を超える Speedup
理論上の上限を超える速度向上(super-linear speedup)が観測されることがあります。主な要因は次の通りです。
- メモリとキャッシュの効果(データが分割されて各ノードのキャッシュに載りやすくなる)
- 並列探索アルゴリズムで早く解が見つかる場合
- 単なる測定上のばらつき(偶然)
2. MPI:仕様と実装の関係
「MPI は specification(仕様)」であり、実際に動くのはその実装です。ここは混同しやすいので分けて整理します。
MPI 仕様の実装(MPI 標準に準拠したライブラリ)
| 実装 | 位置づけ |
|---|---|
| Open MPI | 代表的なオープンソース実装 |
| MPICH | 歴史の古いオープンソースの代表実装 |
| MVAPICH | MPICH ベース。InfiniBand / HPC ネットワーク向けに最適化 |
| Intel MPI | Intel 提供。Intel CPU / Omni-Path 向けに最適化 |
いずれも mpirun / mpiexec を提供し、MPI プログラムを動かすランタイム兼ライブラリです。
GPU 向けコレクティブ通信ライブラリ(MPI 実装ではない)
NCCL / RCCL / HCCL は MPI の実装ではありません。ベンダーごとの GPU/アクセラレータ向け高速コレクティブ通信ライブラリで、allreduce、broadcast、allgather などの集合通信に特化しています。
| ライブラリ | ベンダー / 対象 |
|---|---|
| NCCL | NVIDIA Collective Communications Library(NVIDIA GPU 向け) |
| RCCL | Radeon Collective Communications Library(AMD GPU 向け、NCCL クローン的) |
| HCCL | Huawei Collective Communication Library(Ascend など向け) |
階層イメージ
- MPI(汎用メッセージパッシングの標準仕様)
- Open MPI / MPICH / MVAPICH / Intel MPI(仕様に沿った実装)
- NCCL / RCCL / HCCL(各社が GPU 向けに出す高速集合通信エンジン。規格ではなくプロダクト)
3. MPI Hello World
MPI 入門で最初に出てくる最小プログラムです。mpirun -np N ./a.out で起動すると、同じ実行ファイルが N プロセス立ち上がり、それぞれが自分の rank・ノード名・全体プロセス数を表示します。
int main(int argc, char** argv) {
int world_size, world_rank, name_len;
char processor_name[MPI_MAX_PROCESSOR_NAME];
MPI_Init(NULL, NULL);
MPI_Comm_size(MPI_COMM_WORLD, &world_size);
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
MPI_Get_processor_name(processor_name, &name_len);
printf("Hello world from %s, rank %d/%d\n",
processor_name, world_rank, world_size);
MPI_Finalize();
}
各要素の意味
| 要素 | 役割 |
|---|---|
MPI_Init | MPI ランタイムを初期化。「このプロセスは MPI の一員」と登録する開始宣言 |
MPI_Comm_size | コミュニケータ内の全プロセス数を取得(-np 4 なら 4) |
MPI_Comm_rank | 自分のランク番号(0 〜 world_size-1)を取得。条件分岐(例: rank 0 だけがファイルを読む)に使う |
MPI_Get_processor_name | 動作中の物理マシン名(ホスト名)を取得 |
MPI_COMM_WORLD | 全プロセスが所属する、MPI が用意済みのコミュニケータ |
MPI_MAX_PROCESSOR_NAME | ノード名の最大長を表す MPI 定義の定数 |
MPI_Finalize | MPI の終了処理。以降は原則 MPI 関数を呼べない |
覚え方: MPI プログラムの本編は「MPI_Init 〜 MPI_Finalize の間」。
スライドの色分け: 赤 = MPI 関数、緑 = MPI 定義の定数・ハンドル、黒 = 通常の C 要素。
mpicc hello.c -o hello
mpirun -np 4 ./hello
出力例(どのノードにどの rank が割り当てられたか目視で確認できる):
Hello world from node01, rank 0/4
Hello world from node02, rank 1/4
Hello world from node02, rank 2/4
Hello world from node03, rank 3/4
4. 通信パターンと基本オブジェクト
MPI でできる通信の「地図」です。
通信パターン
| パターン | 代表 API | 概要 |
|---|---|---|
| Two-sided(両側通信) | MPI_Send / MPI_Recv | 送信側と受信側の両方が MPI 関数を呼ぶ。相手が Recv しないと Send が完了しない。ブロッキング/ノンブロッキング(MPI_Isend / MPI_Irecv)を含む |
| One-sided(片側通信) | MPI_Put / MPI_Get | RMA(リモートメモリアクセス)。相手が公開したメモリ領域(MPI_Win_create で作る window)に直接読み書きする。相手は毎回 Recv を書かなくてよい |
| Collective(集合通信) | MPI_Barrier、MPI_Bcast ほか | コミュニケータに属する全員(または一部)が同じ関数を呼んで同期・データ共有する |
| MPI I/O(並列 I/O) | MPI_File_read / MPI_File_write / MPI_File_set_view | 複数プロセスで協調して巨大ファイルを読み書きする。大規模 HPC では I/O がネックになりやすく重要 |
基本オブジェクト
| オブジェクト | 役割 |
|---|---|
| Communicators | プロセスの「グループ」とその中のランク対応をまとめる。MPI_COMM_WORLD が代表。MPI_Comm_split などで部分グループを作れる。「どのグループ内の会話か」を決める部屋番号 |
| Data Types | MPI が理解するデータの並び。基本型(MPI_INT、MPI_DOUBLE など)と派生型(構造体やストライド配列を 1 つの型として定義)。異なるアーキテクチャ間の変換(エンディアン差など)も吸収 |
| Requests | ノンブロッキング通信で使う「未完了操作」を表すハンドル。MPI_Wait / MPI_Test で完了確認。通信と計算のオーバーラップを可能にする |
MPI プログラムは「どのコミュニケータ内で」「どのデータ型のバッファを」「どの通信パターンで」やり取りするか、の 3 つを組み合わせて設計します。
5. Tag-Matching(タグマッチング)
MPI_Send と MPI_Recv は同時に呼ぶ必要はありません。MPI ランタイムが送信キュー・受信キューを管理し、条件が合った瞬間にデータを受信バッファへ渡します。この対応付けが Tag-Matching です。
マッチングに使うパラメータ
| パラメータ | 送信側 | 受信側 | ワイルドカード |
|---|---|---|---|
| 相手ランク | dest | source | MPI_ANY_SOURCE |
| タグ(メッセージ種別のラベル、int 値) | tag | tag | MPI_ANY_TAG |
| コミュニケータ | comm | comm | — |
順序が入れ替わっても正しくつながる
- Recv が先: 受信側が先に
MPI_Recvを呼ぶと「未完了の受信」としてキューに登録され、データ到着時に即バッファへコピーされる。 - Send(データ)が先: データが先に届くと内部バッファにストックされ、後から
MPI_Recvが呼ばれた瞬間にコピーされる。
正しい source, tag, comm さえ指定すれば、送受信のタイミングをぴったり合わせる必要はありません。
マッチングのルール
- マッチング条件(
source,dest,comm,tag)は一意である必要はない。同じ条件のメッセージが複数流れてよい。 - Matching in the order posted: 複数がマッチしうる場合、先にポストされた
Recvから順に、先にポストされたSendとマッチする(FIFO)。これにより「送った順=受け取った順」が保たれ、追い越し(overtaking)が起きない。
アプリからのヒント(info オブジェクト)
コミュニケータに info を付けて「自分はこういうことをしない」と宣言すると、実装がマッチング処理を最適化しやすくなります(実際のキー名・サポート状況は実装依存)。
| ヒント | 宣言する内容 |
|---|---|
mpi_assert_no_any_tag | MPI_ANY_TAG を使わない |
mpi_assert_no_any_source | MPI_ANY_SOURCE を使わない |
mpi_assert_exact_length | 受信 count を常にメッセージ長ぴったりにする |
mpi_assert_no_allow_overtaking | メッセージの追い越しを許さない |
6. ブロッキング Send/Recv の完了条件と引数
ブロッキングな MPI_Send / MPI_Recv は、安全なタイミングまで戻ってこない設計です。
| 関数 | 戻ってきたとき保証されること |
|---|---|
MPI_Recv | 送られてきたデータが受信バッファ(buf)に安全にコピー済み。バッファを読んでよい |
MPI_Send | 送信バッファ(buf)を上書きしても、送信中メッセージが壊れない状態。相手にすでに届いたか、MPI 内部にコピー済み |
注意: 関数が戻る前に、Send は送信バッファを書き換えない、Recv は受信バッファを読まないこと。
引数の役割(3 分割)
int MPI_Send(void* buf, int count, MPI_Datatype datatype,
int dest, int tag, MPI_Comm comm)
int MPI_Recv(void* buf, int count, MPI_Datatype datatype,
int source, int tag, MPI_Comm comm,
MPI_Status* status)
| 分類 | 引数 | 意味 |
|---|---|---|
| データ内容(何を送る/受け取るか) | buf, count, datatype | バッファ先頭、要素数、要素 1 個の型 |
| 相手とグループ(誰と通信するか) | dest / source, comm | 相手ランク、そのランクが意味を持つコミュニケータ |
| タグ(メッセージ種別) | tag | 同じ相手でもタグで別種のメッセージとして区別 |
7. 送信モード(communication modes)
同じ「送る」でも、何をもって送信完了とみなすかがモードで違います。各モードにブロッキング版とノンブロッキング版があります。
| モード | 完了条件 | 特徴・用途 |
|---|---|---|
| Standard(標準) | 実装・メッセージサイズに依存。小さいメッセージは内部バッファにコピーして即完了(eager)、大きいメッセージは受信待ちでブロックすることも | 一番よく使うデフォルト。「本当に届いたか」は保証しない |
| Buffered(バッファ付き) | データがローカルの登録済みバッファ(MPI_Buffer_attach)にコピーされた時点で完了。受信の有無に依存しない | ユーザーが送信用バッファを用意する必要がある |
| Synchronous(同期) | マッチする Recv がポストされ、受信が始まって初めて完了 | 相手の受信開始を確認できる。順序制御やデッドロック発見に有用。遅くなることがある |
| Ready(レディー) | マッチする Recv がすでにポスト済みのときだけ開始してよい。未ポストなら undefined behavior | 特殊・高度な最適化用。一般アプリではほぼ使わない |
モード × ブロッキング / ノンブロッキング
| Standard | Buffered | Synchronous | Ready | |
|---|---|---|---|---|
| Blocking | MPI_Send | MPI_Bsend | MPI_Ssend | MPI_Rsend |
| Nonblocking | MPI_Isend | MPI_Ibsend | MPI_Issend | MPI_Irsend |
8. Point-to-Point 通信の保証
MPI 標準がどこまで保証し、どこから実装依存かを整理します。キーワードは Order / Progress / Fairness / Resource limitations。
| 観点 | 標準の保証 | 実務上のポイント |
|---|---|---|
| Order(順序) | 同じ (source, dest, comm) かつ同種の通信では non-overtaking(送った順に届く) | 「送信順=意味的な順序」として設計してよい |
| Progress(進行性) | MPI 関数を呼んでいるとき以外は通信進行を保証しない | ノンブロッキング通信を投げた後ずっと自前計算だけしていると、背景で転送が進むとは限らない。定期的に MPI_Test / MPI_Wait などで進行のきっかけを与える |
| Fairness(公平性) | 公平性は保証しない | 実装は best effort で公平に動くが、「絶対に公平」と仮定して設計しない |
| Resource limitations(リソース) | 厳密な規定なし(best effort) | 大量の未完了通信を一度に投げるとバッファ不足でエラーや性能劣化。リソースを意識した設計を |
Quality implementation: MPI 標準の保証は最小限(Order 以外はかなり緩い)。実際の良い実装(Open MPI、MPICH、Intel MPI など)は、高速・公平・安定・良い進行性といった望ましい性質を最適化で提供している。「標準の最低ライン」と「実装が提供する挙動」にはギャップがある点に注意。
HPC コンペでチューニングまで踏み込むなら、実装ごとの Progress 性・背景進行の有無はかなり効いてくる。
9. ノンブロッキング通信の完了確認
MPI_Isend / MPI_Irecv は即戻り、MPI_Request を返します。実際の完了は以下のルーティンで確認します。
完了の意味
- 送信(Isend)の完了 → 送信バッファを安全に上書きできる。
- 受信(Irecv)の完了 → 受信バッファにメッセージが入っている。
単一操作の完了
| 関数 | 挙動 |
|---|---|
MPI_Wait(request, status) | その通信 1 個が完了するまでブロック |
MPI_Test(request, flag, status) | ノンブロッキング。flag != 0 なら完了、flag == 0 ならまだ。計算とオーバーラップさせるときに使う |
複数のうちどれか 1 個の完了(ANY)
MPI_Request の配列を渡し、そのうち 1 つが完了したら教えてくれます。
| 関数 | 挙動 |
|---|---|
MPI_Waitany(count, array_of_requests, index, status) | 配列内で最初に完了したものが出るまでブロック。完了した要素の位置が index に入る |
MPI_Testany(count, array_of_requests, index, flag, status) | MPI_Waitany のノンブロッキング版 |
典型パターン: 多数の Isend / Irecv を投げておき、Waitany / Testany で「終わった順」に処理して通信と計算を重ねる。
MPI_Request reqs[N];
/* Isend / Irecv をセットアップして reqs に入れる */
for (int finished = 0; finished < N; ++finished) {
int index;
MPI_Status status;
MPI_Waitany(N, reqs, &index, &status);
/* index 番目の通信の後処理 */
}
10. 集団通信(Collective)
集団通信はコミュニケータ単位で定義されます(defined in the context of a communicator)。そのコミュニケータに属するランクが、全員または特定パターンで同じ collective 関数を呼ぶのが前提です。
10-1. 3 つの分類軸
| 軸 | 種類 | 内容 |
|---|---|---|
| Completion semantics(完了) | Blocking | 1 コミュニケータにつき同時アクティブは 1 つだけ。完了までブロック(MPI_Bcast、MPI_Allreduce、MPI_Barrier) |
| Nonblocking | 同じコミュニケータ上でも複数を同時進行可(MPI_Ibcast、MPI_Iallreduce、MPI_Ibarrier)。計算と通信のオーバーラップに有用 | |
| Scope(スコープ) | Non-persistent | 毎回一から呼ぶ従来型。各操作は一度きり |
| Persistent | テンプレート(persistent object)を一度作り、start / wait を繰り返す。毎回の準備処理を再利用でき、オーバーヘッドを削減 | |
| Participating ranks(参加ランク) | regular | 全ランクが参加(MPI_Bcast、MPI_Gather など)。誰か一人でも呼ばないとデッドロック |
| Neighborhood | トポロジ情報を持つコミュニケータ(MPI_Cart_create など)に紐づき、近傍とだけ通信(MPI_Neighbor_allgather など)。格子状・グラフ状パターンで効率的 |
「nonblocking + persistent + neighborhood」を組み合わせると強力な性能チューニングができる、という布石。
10-2. 入出力パターンによる 4 分類
「誰がデータを出し、誰が結果を受け取るか」で分類します。
| クラス | パターン | 代表 API |
|---|---|---|
| One-To-All | 1 プロセスが情報源 → 全員が受け取る | MPI_Bcast、MPI_Scatter / MPI_Scatterv |
| All-To-One | 全員が出す → 1 プロセスが集約 | MPI_Gather / MPI_Gatherv、MPI_Reduce |
| All-To-All | 全員が出し、全員が受け取る(双方向) | MPI_Allgather / MPI_Allgatherv、MPI_Alltoall / MPI_Alltoallv / MPI_Alltoallw、MPI_Allreduce、MPI_Reduce_scatter |
| Other | 上記に当てはまらないもの | MPI_Scan(prefix 演算 = 部分和)、MPI_Barrier(同期のみ) |
グラフでのイメージ
- One-To-All: ルート 1 個 → 全ノードへ広がる木(Bcast, Scatter)
- All-To-One: 全ノード → ルート 1 個へ集まる木(Gather, Reduce)
- All-To-All: 全ノード間の多重エッジ(Allgather, Alltoall, Allreduce)
- Other: 部分和や同期など、単純な送受図で表しづらいもの(Scan, Barrier)
この型を頭に入れておくと、「Allreduce を Allgather + Reduce に分解できるか」「Bcast の木構造はどうか」といった議論が整理しやすくなる。
大会との関係(メモ)
- OpenFOAM 課題(テーマ①)は分散メモリ並列 = MPI が中核。領域分割・ランク配置・通信量・MPI I/O が性能に直結する。
- 実装ごとの Progress 性・背景進行・コレクティブアルゴリズムの違いは、チューニング時の検討対象になりうる。
- 関連ページ: MPI通信最適化、領域分割(decomposePar)、NCCL通信最適化