並列プログラミングモデル(データ並列・タスク並列・SPMD)
OpenMPやMPI、GPGPUがどの並列モデルに属するのか迷わなくなる。データ並列・タスク並列・SPMD・PGASの分類軸と、各APIの対応関係を整理。
- 1.並列モデルは「何を分割するか」で分類できる。同じ操作を多数のデータへ適用するデータ並列、独立な処理単位を分割するタスク並列、全プロセスが同一プログラムを別データで走らせるSPMDが基本三系統。
- 2.OpenMPはデータ並列+タスク並列を共有メモリで、MPIはSPMDを分散メモリのメッセージパッシングで、GPGPU(CUDA/OpenCL)はSIMTによる大規模データ並列を担う。
- 3.PGASは分散メモリを単一のグローバルアドレス空間に見せる中間形で、局所性を意識しつつ共有メモリ風に書ける。モデルとAPIは多対多で対応する。
なぜ「モデル」で分類するのか
並列化を始めると、最初に決めるべきは「何を分割するか」です。同じ計算を大量のデータへ一斉に適用するのか、互いに独立した別々の仕事を同時に進めるのか、で設計が根本から変わります。この「分割の対象」が並列プログラミングモデルの分類軸であり、OpenMP・MPI・CUDA といった具体的な API は、いずれかのモデルを実装した道具にすぎません。
並行性(concurrency)が「複数の処理を構造的に扱う」話なのに対し、並列性(parallelism)は「複数の演算資源で物理的に同時実行して速くする」話です。本記事は後者、つまり性能のための分割に焦点を当てます。構造としての並行性は並行性モデル(CSP・アクター・STM)を参照してください。
モデルは概ね次の軸で見分けられます。(1) 分割対象はデータかタスク(制御)か。(2) メモリは共有か分散か。(3) 同期は暗黙か明示的なメッセージパッシングか。これらの組み合わせとして、データ並列・タスク並列・SPMD・PGAS が位置づきます。
系統と分岐(年代で追う)
各モデルがどの時代に、何を解くために生まれたかを押さえると狙いが見えます。
- 1960年代後半:Flynn が SISD/SIMD/MIMD/MISD の分類を提示。並列アーキテクチャの語彙の出発点。
- 1980年代:ベクトル計算機と SIMD によるデータ並列が科学技術計算で主流に。
- 1990年代前半:分散メモリ機の普及を受け、SPMD を前提とする MPI(Message Passing Interface)が標準化される。
- 1997年:共有メモリ機向けに OpenMP が登場。逐次コードへ指示文(pragma)を足して段階的に並列化する方式。
- 2000年代後半:GPU を汎用計算に使う GPGPU が CUDA(2007)・OpenCL(2009)で実用化。SIMT という大規模データ並列の実行形。
- 2000年代以降:UPC や Coarray Fortran などの PGAS が、分散メモリを単一アドレス空間に見せる中間形として整備される。
| 年代 | モデル/規格 | 中心概念 | メモリ前提 |
|---|---|---|---|
| 1990s | MPI | SPMD+メッセージパッシング | 分散メモリ |
| 1997 | OpenMP | 指示文によるデータ/タスク並列 | 共有メモリ |
| 2007 | CUDA | SIMTによる大規模データ並列 | GPUデバイスメモリ |
| 2000s | PGAS (UPC等) | 分割グローバルアドレス空間 | 論理共有・物理分散 |
データ並列:同じ操作を多数の要素へ
データ並列モデルでは、一つの操作を巨大な配列の各要素へ一斉に適用します。要素ごとの計算が独立なら、要素を演算資源へ均等に割り当てるだけで並列化できます。ループの繰り返しが互いに依存しない(ループ運搬依存がない)ことが前提です。
# 逐次:N回のループ
for i in 0..N:
c[i] = a[i] + b[i]
# データ並列:iの範囲を演算資源へ分割し同時実行
parallel_for i in 0..N:
c[i] = a[i] + b[i] # 各iは独立、互いに干渉しない
Flynn 分類の SIMD(Single Instruction, Multiple Data)はこの考えのハードウェア化で、1命令が複数データレーンを同時に処理します。CPU の SSE/AVX、GPU の演算ユニットがその実体です。データ並列は規則的で要素数が多い問題(行列演算・画像処理・物理シミュレーション)で威力を発揮し、要素数に応じてスケールしやすい点が長所です。
総和や最大値のように全要素を一つの値へ畳み込むリダクションは、各要素が同じ変数を更新するため素朴には並列化できません。部分和を演算資源ごとに別々に計算し、最後に木状にまとめる(並列リダクション)必要があります。OpenMP の reduction 句や GPU のリダクションカーネルはこの定石を提供します。
タスク並列:独立な処理単位を分割
タスク並列モデルでは、分割するのはデータではなく制御です。互いに独立した(あるいは依存関係が明示された)処理単位=タスクを切り出し、それぞれを別の演算資源へ割り当てます。各タスクが異なるコードを実行してよい点がデータ並列との決定的な違いで、Flynn 分類の MIMD(Multiple Instruction, Multiple Data)に対応します。
# 分割統治:左右の半分を独立タスクとして並列実行
task_a = spawn quicksort(arr, lo, mid) # 別タスクへ
quicksort(arr, mid, hi) # 現タスクで継続
sync task_a # 両者の完了を待ち合わせ
実装はタスクキュー+ワーカースレッドが定番で、生成したタスクをスケジューラが空いた資源へ動的に割り当てます。負荷が偏らないよう、暇なワーカーが他人のキュー末尾から仕事を奪うワークスティーリングが広く使われます。仕組みの詳細はスレッドプールとワークスティーリングを参照してください。タスク並列は分割統治・再帰・依存グラフ(DAG)型のワークロードに向きますが、タスク粒度が細かすぎると生成・同期のオーバーヘッドが利得を食い潰します。
| 観点 | データ並列 | タスク並列 |
|---|---|---|
| 分割の対象 | データ要素 | 処理単位(タスク) |
| 各単位の処理 | 同一の操作 | 異なってよい |
| Flynn分類 | SIMD寄り | MIMD |
| 向く問題 | 規則的・大量要素 | 分割統治・依存グラフ |
| 代表手段 | AVX / GPU / OpenMP for | タスクキュー / OpenMP task |
SPMD:単一プログラムを別データで多重に走らせる
SPMD(Single Program, Multiple Data)は、全プロセスがまったく同じプログラムを起動し、自分の識別番号(ランク)に応じて担当データと分岐を変える実行スタイルです。MIMD ハードウェア上の最も実用的なプログラミング様式で、HPC の事実上の標準になっています。SIMD が「1命令を同時実行」というハードの話なのに対し、SPMD は「同一コードを多重起動」というソフトの構成法である点を混同しないでください。
# 全プロセスが同一コードを実行。rank で役割が分かれる
rank = comm_rank() # 自分の番号(0..size-1)
n = comm_size() # 総プロセス数
my_chunk = partition(data, rank, n) # 担当範囲を自分で決める
local = compute(my_chunk)
total = all_reduce(local, SUM) # 全プロセスで集約・全員が結果を得る
分散メモリ上ではプロセス間でアドレス空間が分かれているため、データ交換は明示的なメッセージパッシング(送信・受信、集団通信)で行います。これを担う標準が MPI です。SPMD は数千〜数万プロセスへスケールできる一方、通信回数と通信量がボトルネックになりやすく、領域分割と通信の重ね合わせ(オーバーラップ)が性能の鍵になります。
名前が似ていますが層が違います。SIMD は1つの命令流が複数データレーンを駆動するハード機構、SPMD は独立した複数プロセスが各自の命令流で同一プログラムを走らせるソフト構成です。SPMD の各プロセスは別々に分岐でき、内部で SIMD を併用することもできます。
PGAS:分散メモリを単一アドレス空間に見せる
PGAS(Partitioned Global Address Space、分割グローバルアドレス空間)は、物理的には分散したメモリを論理的に一つのグローバル配列として扱えるようにするモデルです。各データには「どのプロセスに属するか」という**親和性(affinity)**の概念があり、ローカル参照は速く、リモート参照は通信を伴います。プログラマは共有メモリ風に添字でアクセスしつつ、局所性を意識して通信量を抑えます。
メッセージパッシングを書かずに済む書きやすさと、局所性を制御できる性能透明性の中間を狙った設計で、UPC・Coarray Fortran・Chapel などが代表です。MPI ほど通信が明示的でない分、暗黙のリモートアクセスが性能の落とし穴になりやすい点には注意が要ります。
モデルと API の対応(多対多)
重要なのは、モデルと API は1対1で対応しないことです。一つの API が複数モデルをまたぎ、一つのモデルが複数 API で実現されます。
| API/環境 | 主なモデル | メモリ | 同期/通信 |
|---|---|---|---|
| OpenMP | データ並列+タスク並列 | 共有メモリ | 暗黙バリア・指示文 |
| MPI | SPMD | 分散メモリ | 明示的メッセージパッシング |
| CUDA / OpenCL | データ並列(SIMT) | デバイスメモリ | カーネル起動・バリア |
| UPC / Coarray | PGAS | 論理共有・物理分散 | 片側通信(put/get) |
| MPI+OpenMP | SPMD+共有メモリ並列 | ハイブリッド | ノード間MPI・ノード内OpenMP |
GPGPU の SIMT(Single Instruction, Multiple Threads)は、多数のスレッドをワープ(32スレッド単位)でまとめ、ワープ内は1命令を同時実行する点で SIMD のデータ並列に近い一方、スレッドごとに分岐できる柔軟さを持ちます。ただし分岐が割れると(ワープダイバージェンス)両方の経路を順に実行して性能が落ちるため、データ並列の「規則性」が依然として効いてきます。
頻出は「SIMDとSPMDの違い」と「各APIがどのモデルか」です。SIMDは1命令×複数データのハード機構、SPMDは同一プログラム×複数プロセスのソフト構成。OpenMP=共有メモリのデータ/タスク並列、MPI=分散メモリのSPMD、CUDA=SIMTの大規模データ並列、PGAS=分割グローバルアドレス空間——この対応を即答できるように。データ並列は「同じ操作を多数要素へ」、タスク並列は「異なる処理を独立単位へ」が核心です。
どう選ぶか
唯一の正解はなく、問題の規則性・データの規模・ハード構成で決まります。
- データ並列(GPGPU/SIMD):要素数が膨大で各要素の計算が同型・独立な問題(行列・画像・ディープラーニング)。
- タスク並列:分割統治や依存グラフ型で、各単位の処理内容が異なる問題。
- SPMD(MPI):単一ノードに収まらない大規模問題を多数ノードへ分散する HPC。
- PGAS:分散規模が要るがメッセージパッシングの記述コストを抑えたい局面。
- ハイブリッド(MPI+OpenMP+GPU):ノード間はSPMD、ノード内は共有メモリ並列、演算はGPUデータ並列、という三層構成が現代のスパコンの定石。
どのモデルでも、得られる速度向上は逐次部分に頭打ちされます。並列化前に上限を見積もる発想はアムダールの法則・グスタフソンの法則に、共有メモリ並列での待ち合わせの基礎は同期プリミティブ(mutex・セマフォ・条件変数)にまとまっています。モデル選択の出発点は一つ——**「何を分割すれば、各単位が独立になるか」**を見極めることです。
プログラミング Article
並列プログラミングモデル(データ並列・タスク並列・SPMD)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
並列処理
比較で見る軸
難易度: advanced / カテゴリ: プログラミング / タグ数: 5
導入後に効く点
OpenMPはデータ並列+タスク並列を共有メモリで、MPIはSPMDを分散メモリのメッセージパッシングで、GPGPU(CUDA/OpenCL)はSIMTによる大規模データ並列を担う。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- プログラミング
- タグ数
- 5
判断チェックリスト
- 自社の用途が「並列処理 / OpenMP」に近いか確認する。
- 強みである「並列モデルは「何を分割するか」で分類できる。同じ操作を多数のデータへ適用するデータ並列、独立な処理単位を分割するタスク並列、全プロセスが同一プログラムを別データで走らせるSPMDが基本三系統。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。