1
2
並列処理プログラミング
逐次型 並列型 逐次言語 拡張言語 並列言語 逐次言語+並列化ライブラリ 並列性解析・抽出 タスクスケジューリング マシンコード生成 解くべき問題 OS 並列コンピュータアルゴリズム
プログラミング
言語
並列化
コンパイラ
3
並列処理プログラミング
逐次型 並列型 逐次言語 拡張言語 並列言語 逐次言語+並列化ライブラリ 並列性解析・抽出 タスクスケジューリング マシンコード生成 解くべき問題アルゴリズム
プログラミング
言語
並列化
コンパイラ
並列化は行われていない
OS 並列コンピュータ4
並列処理プログラミング
逐次型 並列型 逐次言語 拡張言語 並列言語 逐次言語+並列化ライブラリ 並列性解析・抽出 タスクスケジューリング マシンコード生成 解くべき問題アルゴリズム
プログラミング
言語
並列化
コンパイラ
人手による
並列化が必要
OS 並列コンピュータ5
並列処理プログラミング
逐次型 並列型 逐次言語 拡張言語 並列言語 逐次言語+並列化ライブラリ 並列性解析・抽出 タスクスケジューリング マシンコード生成 解くべき問題アルゴリズム
プログラミング
言語
並列化
コンパイラ
並列型アルゴリズムが必要
OS 並列コンピュータアルゴリズムの並列性
以外の並列化は必要
6
並列処理プログラミング
逐次型 並列型 逐次言語 拡張言語 並列言語 逐次言語+並列化ライブラリ 並列性解析・抽出 タスクスケジューリング マシンコード生成 解くべき問題アルゴリズム
プログラミング
言語
並列化
コンパイラ
並列化のすべてを
コンパイラが行う
並列化は行われていない
OS 並列コンピュータ7
並列処理プログラミング
逐次型 並列型 逐次言語 拡張言語 並列言語 逐次言語+並列化ライブラリ 並列性解析・抽出 タスクスケジューリング マシンコード生成 解くべき問題アルゴリズム
プログラミング
言語
並列化
コンパイラ
残された並列化を
コンパイラが行う
並列化が行われている
OS 並列コンピュータ8
並列処理プログラミング
逐次型 並列型 逐次言語 拡張言語 並列言語 逐次言語+並列化ライブラリ 並列性解析・抽出 タスクスケジューリング マシンコード生成 解くべき問題アルゴリズム
プログラミング
言語
並列化
コンパイラ
(一部)並列化が行われている
OS 並列コンピュータ並列化のための書換え
9
並列処理プログラミングモデル
• プログラミングモデル(ソフトウェア的な通信モデル)
の観点からは次の二つに分類される.
– 共有変数型(単一メモリ空間型)
– メッセージ転送型
10
共有変数型
• 異なるプロセッサ上のプロセス間で変数を共有
共有変数を介してプロセス間通信
→ ポインタ変数などの扱いが楽
• 共有メモリ型や分散共有メモリ型並列計算機上で用
いられることが多い
物理的にメモリを共有する必要は必ずしも無い
→ OSサポートなど
• 分散メモリ型並列計算機での実装では性能低下は大
P0
P1
共有変数
代入
参照
11
メッセージ転送型
• 異なるプロセッサ上のプロセス間での通信手段
→メッセージ転送のみ
変数のパッキングなどが必要
• 送信側と受信側のプロセッサが協調してデータ転送
• 分散メモリ型並列計算機上で用いられることが多い
共有メモリ型並列計算機でも実現可能
→ 共有メモリ上に通信チャネルを用意する
P0
P1
send
receive
12
• ハードウェア構成とプログラミングモデルを無関係に
したい.
– ハードのメモリ制御(キャッシュ制御を含む)機構や
通信制御機構
– 通信ライブラリ
– ソフトウェア分散共有メモリ
– コンパイラ
• パフォーマンス向上にはH/W構成に適したプログラミ
ングが必要.
プログラミングモデルとH/Wの構成
-PGAS: Partitioned Global Address Space モデル
13
並列処理を可能とするOS環境
• 並列コンピュータ上での並列処理を実現するOS機能
• プロセス間並列(マルチタスキング)
– 単一PEでの複数プロセスの並行処理の発展形
– プログラム中のタスク群を複数のプロセスに割り当
て,それらを複数プロセッサで実行する.
• スレッド間並列(マルチスレッディング)
– ひとつのプロセスをさらにスレッドに分割しそれらを
複数PEで実行する.
– プログラム中のタスク群はスレッド群に割り当て,そ
れらを複数PEで実行する.
14
プロセス間並列
• 例) 1PEでの複数プロセスの並行処理
プロセスa2 プロセスa1 時間 実行中 アイドル コンテキスト スイッチ プロセスb プロセス生成15
• 例) 1PEでの複数プロセスの並行処理
• 例) 2PEでの複数プロセスの並列/並行処理
プロセス間並列
プロセスa2 プロセスa1 時間 実行中 アイドル プロセスb プロセスa3 時間 プロセスa2 プロセスa1 コンテキスト スイッチ PE1 PE2 プロセスb プロセス生成16
プロセス間並列
• プロセスの生成・終了・待合せのための機能
– fork(), exit(), wait()などの関数
• プロセス間データ授受(IPC)のための機能
– データ転送
「パイプ」,「ソケット」,「メッセージキュー」など
– データ共有
共有メモリ領域:
複数プロセスのメモリ空間の一部をオーバーラップ
– 同期
「シグナル」,「セマフォ」など
• 各種操作のコストが大きい.
– プロセス生成,コンテキストスイッチ,同期,通信
17
スレッド間並列(マルチスレッド:MT)
• スレッド:
– 同一プロセス内で複数制御フロー(スレッド)を用意.
– 個別の制御フローを個別のスレッドに対応させる.
– スレッドをPEへのスケジュール単位とする.
– 同一プロセスのスレッドはアドレス空間を共有.
→ メモリ管理の負荷が小さい
→ 通信・同期のコストが小さい
– スレッド固有情報(プログラムカウンタ,スタックポイン
タ,レジスタセット)がプロセス情報(アドレス空間,ユ
ーザID,etc.)より少ない.
→ スレッド生成や各種操作のコストが小さい.
18
スレッド間並列(マルチスレッド:MT)
• スレッド:
プロセスb スレッド3 時間 スレッド2 スレッド1 PE1 PE2 プロセスa 実行中 アイドル コンテキスト スイッチ スレッド生成 スレッド119
並列プログラミング環境
• 逐次言語 + マルチタスキング
• 逐次言語 + マルチスレッド
• 逐次言語 + メッセージ通信ライブラリ
例)
MPI (Message Passing Interface)
• 逐次言語 + コンパイラディレクティブ(+α)
例)
OpenMP,OpenACC
• 並列言語
例)
HPF (High Performance Fortran)
20
• 参考書/例題プログラムの出典
「はじめての並列プログラミング」
21
• forkシステムコールにより複数プロセスを立ち上げての
並列処理(並行処理)
• (親)プロセスがfork関数を呼び出すと,
– 子プロセスが生成される.
子プロセス環境は親プロセスの環境が複製される.
– 親プロセスと子プロセスはfork関数呼出しから戻った
ところからそれぞれ実行を再開.
– fork関数の戻り値は,子プロセスでは0となり,親プロ
セスでは子プロセスのプロセスIDとなる.
• 子プロセスでは,処理終了後exit()システムコールなど
でプロセスを終了する.
• 親プロセス,子プロセス間では共有変数などを用いて
データの授受を行う.
マルチタスキングによる並列処理
22
マルチタスキング-例題プログラム
#include <sys/shm.h> #include <sys/types.h> #include <sys/ipc.h> #include <stdio.h> pid_t pid1, pid2; int shared_mem_id; int *shared_mem_ptr; int main(){
int *rc1, *rc2;
int arg1[2] = {1,5}, arg2[2] = {6,10}; int status; shared_mem_id=shmget(IPC_PRIVATE, 2*sizeof(int),0666); shared_mem_ptr=shmat(shared_mem_id,0,0); rc1 = shared_mem_ptr; rc2 = (shared_mem_ptr+1);
和を部分和として二つの
プロセスで求めるプログラム
続く
23
マルチタスキング-例題プログラム
if((pid1=fork())==0){ *rc1=sum(&arg1); exit(0); } if((pid2=fork())==0){ *rc2=sum(&arg2); exit(0); } waitpid(pid1, status, 0); waitpid(pid2, status, 0); printf("%d %d¥n", *rc1 ,*rc2); printf("%d+..+%d=%d¥n", arg1[0],arg2[1], *rc1 + *rc2); }和を部分和として二つの
プロセスで求めるプログラム
続く
int sum(int *arg_ptr) {
int min = *arg_ptr;
int max = *(arg_ptr+1); int i, sum;
for (i=min,sum =0;i<=max;i++) sum += i;
return sum; }
24
マルチタスキング-例題プログラム
#include <sys/shm.h> #include <sys/types.h> #include <sys/ipc.h> #include <stdio.h>pid_t pid1, pid2;
int shared_mem_id; int *shared_mem_ptr; int main()
{
int *rc1, *rc2;
int arg1[2] = {1,5}, arg2[2] = {6,10}; int status; shared_mem_id=shmget(IPC_PRIVATE, 2*sizeof(int),0666); shared_mem_ptr=shmat(shared_mem_id,0,0); rc1 = shared_mem_ptr; rc2 = (shared_mem_ptr+1);
和を部分和として二つの
プロセスで求めるプログラム
続く
ヘッダファイルの読み込み
プロセスID変数
共有変数管理のための変数
共有変数領域IDの確保
共有変数へのポインタ変数
共有変数領域開始アドレス
25
マルチタスキング-例題プログラム
if((pid1=fork())==0){ *rc1=sum(&arg1); exit(0); } if((pid2=fork())==0){ *rc2=sum(&arg2); exit(0); } waitpid(pid1, status, 0); waitpid(pid2, status, 0); printf("%d %d¥n", *rc1 ,*rc2); printf("%d+..+%d=%d¥n", arg1[0],arg2[1], *rc1 + *rc2); }和を部分和として二つの
プロセスで求めるプログラム
続く
子プロセスならsumを実行し
結果を共有変数へ代入
親プロセスは子プロセスIDを得る
子プロセスの終了を待つ
子プロセスの終了を待つ
子プロセスを生成:
戻り値は子プロセスには0、
親プロセスには子プロセスID
子プロセスならsumを実行し
結果を共有変数へ代入
親プロセスは子プロセスIDを得る
共有変数を参照する
26
マルチタスキング-例題プログラム
int sum(int *arg_ptr){
int min = *arg_ptr;
int max = *(arg_ptr+1); int i, sum;
for (i=min, sum =0; i<= max; i++) sum += i;
return sum; }
和を部分和として二つの
27
• プロセス間での
– 同期(セマフォ): semop関数など
– データ授受:
msgsnd/msgrcv関数など
28
• スレッドライブラリを使用しスレッドコントロール
• スレッドライブラリはスレッドコントロールのためのAPIを
提供している.
29
MT-例題プログラム
#include <pthread.h>#include <stdio.h>
extern int *sum(int *); pthread_t th1, th2;
int main() {
int *ps1, *ps2;
int arg1[2]={1,5}, arg2[2] = {6,10};
pthread_create(&th1,NULL,(void*(*)(void*))sum,&arg1); pthread_create(&th2,NULL,(void*(*)(void*))sum,&arg2);
pthread_join(th1, (void**)&ps1); pthread_join(th2, (void**)&ps2);
printf("%d+..+%d=%d¥n", arg1[0], arg2[1], *ps1+*ps2); free(ps1); free(ps2);
}
和を部分和として二つの
スレッドで求めるプログラム
30
int *sum(int *arg_ptr) {
int lb = *arg_ptr;
int ub = *(arg_ptr+1); int i, sum;
int *p;
for (i=lb, sum =0; i<= ub; i++) { sum += i;} p =(int *)malloc(sizeof(int)); *p = sum; return p; }
MT-例題プログラム
和を部分和として二つの
スレッドで求めるプログラム
31
MT-例題プログラム
#include <pthread.h>#include <stdio.h>
extern int *sum(int *); pthread_t th1, th2;
int main() {
int *ps1, *ps2;
int arg1[2]={1,5}, arg2[2] = {6,10};
pthread_create(&th1,NULL,(void*(*)(void*))sum,&arg1); pthread_create(&th2,NULL,(void*(*)(void*))sum,&arg2);
pthread_join(th1, (void**)&ps1); pthread_join(th2, (void**)&ps2);
printf("%d+..+%d=%d¥n", arg1[0], arg2[1], *ps1+*ps2); free(ps1); free(ps2); }
和を部分和として二つの
スレッドで求めるプログラム
続く
ヘッダファイルの読み込み
二つのスレッド生成
スレッド開始関数への引数
スレッド開始関数
スレッドID変数
スレッドの終了待ち
スレッド終了状態
32
int *sum(int *arg_ptr) {
int lb = *arg_ptr;
int ub = *(arg_ptr+1); int i, sum;
int *p;
for (i=lb, sum =0; i<= ub; i++) { sum += i;}
p =(int *)malloc(sizeof(int)); *p = sum; return p; }
MT-例題プログラム
和を部分和として二つの
スレッドで求めるプログラム
スレッドローカルな変数
pが終了ステータスとして通知される
pthread_exit(p);でもOK
スレッド外からもアクセス
できるように
33
• スレッド間の同期
– 相互排除
pthread_mutex_lock(&mt)
pthread_mutex_unlock(&mt)
pthread_mutex_trylock(&mt)
mtは同期変数: pthread_mutex_t mt
– 条件同期
pthread_cond_wait(&ct, &mt)
pthread_cond_signal(&mt)
pthread_cond_broadcast(&mt)
ctは同期変数: pthread_cond_t ct
– など
マルチスレッディングによる並列処理
34
MT-相互排除
#include <pthread.h> #include <stdio.h>extern int *sum(int *); pthread_t th1, th2; pthread_mutex_t mt = PTHREAD_MUTEX_INITIALIZER; int gsum; int main() { int *ps1, *ps2;
int arg1[2]={1,5}, arg2[2] = {6,10};
pthread_create(&th1,NULL,(void*(*)(void*))sum,&arg1); pthread_create(&th2,NULL,(void*(*)(void*))sum,&arg2); pthread_join(th1, (void**)&ps1);
pthread_join(th2, (void**)&ps2);
printf("%d+..+%d=%d¥n", arg1[0], arg2[1], gsum); free(ps1); free(ps2); }
和を部分和として二つの
スレッドで求めるプログラム
続く
または pthread_mutex_init(&mt, NULL);35
int *sum(int *arg_ptr) {
int lb = *arg_ptr;
int ub = *(arg_ptr+1); int i, sum;
for (i=lb, sum =0; i<= ub; i++) { sum += i;}
pthread_mutex_lock(&mt); gsum=gsum+sum; pthread_mutex_unlock(&mt); return 0; }
MT-相互排除
和を部分和として二つの
スレッドで求めるプログラム
36
• メッセージ通信ライブラリ(のAPI
仕様)
プロセス間でのデータ授受のための通信関数のライブ
ラリ(百数十)
[1].
• バージョン
1994 May MPI 1.0
・・・
2015 June MPI 3.1
MPI 4.0
• 複数プロセスが協調して動作する並列実行モデル
プログラム開始時に複数プロセスが一斉に実行を開
始し,一斉に終了する(MPI-1)
例) mpirun
–np 8 my_program
[1] http://www.mpi-forum.org/ MPI Forum
37
• メッセージは次の三つの組で指定される
– 通信範囲を示すプロセスグループ(コミュニケータ)
– プロセスグループ中でのプロセスID(ランク)
– 通信の識別子(タグ)
MPI(Message-Passing Interface)
38
MPIー例題プログラム
#include “mpi.h”int main(int argc, char **argv) { int myrank, error, buffer
MPI_Status status;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &myrank); if (myrank == 0) {
error = MPI_Send(&buffer, 1, MPI_INT,
1, 1234, MPI_COMM_WORLD); } else if (myrank == 1) {
error = MPI_Recv(&buffer, 1, MPI_INT,
0, 1234, MPI_COMM_WORLD, &status); } MPI_Finalize(); }
プロセス間でデータを
授受するプログラム
39
MPIー例題プログラム
#include “mpi.h”int main(int argc, char **argv) { int myrank, error, buffer
MPI_Status status;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &myrank); if (myrank == 0) {
error = MPI_Send(&buffer, 1, MPI_INT,
1, 1234, MPI_COMM_WORLD); } else if (myrank == 1) {
error = MPI_Recv(&buffer, 1, MPI_INT,
0, 1234, MPI_COMM_WORLD, &status); } MPI_Finalize(); }
MPIプログラムの全体の枠組み
ヘッダファイルの読み込み
MPIライブラリの初期化
MPIライブラリの終了処理
40
MPIー例題プログラム
#include “mpi.h”int main(int argc, char **argv) { int myrank, error, buffer
MPI_Status status;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &myrank); if (myrank == 0) {
error = MPI_Send(&buffer, 1, MPI_INT,
1, 1234, MPI_COMM_WORLD); } else if (myrank == 1) {
error = MPI_Recv(&buffer, 1, MPI_INT,
0, 1234, MPI_COMM_WORLD, &status); } MPI_Finalize(); }
バッファの指定:先頭アドレス,個数,型
メッセージの送受信
送受信で指定する情報
相手と文脈の指定:ランク,タグ,コミュニケータ
41
MPIー例題プログラム
#include “mpi.h”int main(int argc, char **argv) { int myrank, error, buffer
MPI_Status status;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &myrank); if (myrank == 0) {
error = MPI_Send(&buffer, 1, MPI_INT,
1, 1234, MPI_COMM_WORLD); } else if (myrank == 1) {
error = MPI_Recv(&buffer, 1, MPI_INT,
0, 1234, MPI_COMM_WORLD, &status); } MPI_Finalize(); }
バッファの指定:先頭アドレス,個数,型
受信状態
メッセージの送受信
送受信で指定する情報
相手と文脈の指定:ランク,タグ,コミュニケータ
受信メッセージのランクやタグ(ワイルドカード受信の際に利用)など42
MPIー例題プログラム
#include “mpi.h”int main(int argc, char **argv) { int myrank, error, buffer
MPI_Status status;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &myrank); if (myrank == 0) {
error = MPI_Send(&buffer, 1, MPI_INT,
1, 1234, MPI_COMM_WORLD); } else if (myrank == 1) {
error = MPI_Recv(&buffer, 1, MPI_INT,
0, 1234, MPI_COMM_WORLD, &status); } MPI_Finalize(); }
自プロセスのランクの取得
自分のランクが0の場合
プロセスの識別
プログラム中の各プロセスにラ
ンクが付加されそれで区別する
自分のランクが1の場合
43
MPIー双方向通信例題プログラム
• 双方向送受信をしたい.次のコードは動作するか?
if (myrank == 0) { MPI_Send(&sb, 1, MPI_INT, 1, 1234, MPI_COMM_WORLD); MPI_Recv(&rb, 1, MPI_INT, 1, 1234, MPI_COMM_WORLD, &status); } else if (myrank == 1) { MPI_Send(&sb, 1, MPI_INT, 0, 1234, MPI_COMM_WORLD); MPI_Recv(&rb, 1, MPI_INT, 0, 1234, MPI_COMM_WORLD, &status); }ブロッキングsend/receiveのためデッドロック!
44
MPIー双方向通信例題プログラム
• 双方向送受信をしたい.次のコードは動作するか?
if (myrank == 0) { MPI_Recv(&rb, 1, MPI_INT, 1, 1234, MPI_COMM_WORLD, &status); MPI_Send(&sb, 1, MPI_INT, 1, 1234, MPI_COMM_WORLD); } else if (myrank == 1) { MPI_Recv(&rb, 1, MPI_INT, 0, 1234, MPI_COMM_WORLD, &status); MPI_Send(&sb, 1, MPI_INT, 0, 1234, MPI_COMM_WORLD); }send/receiveの順序を入れ替えてもだめ
45
MPIー双方向通信例題プログラム
• 双方向送受信をしたい.次のコードは動作するか?
if (myrank == 0) { MPI_Send(&sb, 1, MPI_INT, 1, 1234, MPI_COMM_WORLD); MPI_Recv(&rb, 1, MPI_INT, 1, 1234, MPI_COMM_WORLD, &status); } else if (myrank == 1) { MPI_Recv(&rb, 1, MPI_INT, 0, 1234, MPI_COMM_WORLD, &status); MPI_Send(&sb, 1, MPI_INT, 0, 1234, MPI_COMM_WORLD); }双方の順序を逆にする必要
46
MPIー双方向通信例題プログラム
ノンブロッキングのIsendとWait
if (myrank == 0) { MPI_Isend(&sb, 1, MPI_INT, 1, 1234, MPI_COMM_WORLD, &id); MPI_Recv(&rb, 1, MPI_INT, 1, 1234, MPI_COMM_WORLD, &rstatus);MPI_Wait(&id, &wstatus); } else if (myrank == 1) {
MPI_Isend(&sb, 1, MPI_INT,
0, 1234, MPI_COMM_WORLD, &id); MPI_Recv(&rb, 1, MPI_INT,
0, 1234, MPI_COMM_WORLD, &status);
MPI_Wait(&id, &wstatus); }