次に、処理単位並列化をしてみましょう。以下に処理(ブロック)単位に並列化する際の記述を記します。まず、「#pragma omp parallel」と「#pragma omp sections」の記述を行い、セクション指定を行う範囲を記します。そして、その直下で「#pragma omp section」を使用して、ブロック(セクション)ごとの並列化記述を行います。ちなみに、“section”ディレクティブは、 “sections” ディレクティブの直下でのみ入れ子にして記述します。
「#pragma omp sections」指示文ではブロック内を並列に処理します。必ず宣言子「#pragma omp section」とともに使われます。その宣言子「#pragma omp section」の次の1文またはブロックを1つのスレッドに割り当てて並列に実行します。なお、「#pragma omp section」の後の1文またはブロックをセクションと呼びます。
あるスレッドが「Hello 1」と表示、別のスレッドが「Hello 2」と表示、さらに別のスレッドが「Hello 3」を2回表示していることを確認してください。また、各ブロック(セクション)は並列で動作されるため、表示される順番は、「Hello 1」→「Hello 3」→「Hello 2」→「Hello 3」のように、逐次処理とは違っていることも確認してください。
並列リージョンの中に指示文が「#pragma omp sections」指示文が1つしかない場合には、下のプログラムのように結合指示文「#pragma omp parallel sections」を使って短く記述することも可能となります。セクション部分では、当然ながら関数呼び出しを行うことも可能です。そうすると、各スレッドでまったく独立した処理を実行するようなことも可能となります。
OpenMP の構文は、主に、「指示文」「指示節」「実行時ライブラリ関数」「環境変数」の4つになります。
「指示文」は先ほど説明したもので、プログラム内で並列化を行う場所に挿入して並列化の方法を指定します。「指示節」は必ずOpenMPの指示文とともに使われ、指示文の詳細を指定するものです。また、より高度な並列化を行うために、現在起動しているスレッドの数の参照などがサポートされた「実行時ライブラリ関数」と、使用するスレッド数の指定などがサポートされた「環境変数」などが提供されています。
OpenMPの指示文は、プログラム内で並列化を行う場所に挿入して並列化の方法を指定します。プラグマ(#pragma)によって記述され、必ず「#pragma omp…」のような形を取ります。
(1)並列リージョン指示文
#pragma omp parallel | 並列領域を定義します。スレッドが生成され実行されます |
---|---|
(2)処理分散指示文
#pragma omp for | 直後のforループの繰り返しを並列実行するように指定します |
---|---|
#pragma omp sections | チームのスレッドとして分割されるコードのブロックを指定します(ワークシェアリング領域) |
(3)同期に関する指示文
#pragma omp single | チーム内の 1つのスレッドでのみ実行されるコードのブロックを指定します |
---|---|
#pragma omp master | チームのマスタスレッドにより実行されるコードのブロックを指定します |
#pragma omp critical | コードのブロックの実行を一度に 1つのスレッドだけに制限します |
#pragma omp atomic | 特定のメモリー位置が動的に更新されるように指定します。※複数スレッドの同時書き込みを防ぐ際に使用する |
#pragma omp barrier | 明示的にバリアを使用する際に指定します |
#pragma omp ordered | 並列実行せず逐次実行するコードのブロックを指定します |
#pragma omp flush | スレッドの一時メモリーの内容を、メモリーにフラッシュし一貫性のある状態にします |
(4)結合指示文(並列リージョン指示文と処理分散指示文を結合したもの)
#pragma omp parallel for | 並列領域を定義し、直後の forループの繰り返しを並列実行するように指定します |
---|---|
#pragma omp parallel sections | 並列領域を定義し、チームのスレッド間で分割されるコードのブロックを指定します(ワークシェアリング領域) |
(5)データ属性指示文
#pragma omp threadprivate | スレッド内でのプライベートとするために、個別のコピーを持ちます。また、ほかのスレッドに対しては非公開になります |
---|---|
指示節は必ずOpenMP の指示文とともに使われ、指示文の詳細を指定します。記述は、「#pragma omp……指示節」のような形となります。
(1)スコープ指示節
private(list) | listに指定された変数が各スレッドでプライベートであることを宣言します |
---|---|
firstprivate(list) | listに指定された変数はprivate 指示節と同様、プライベートであることを宣言します。また、初期値設定が行えます |
lastprivate(list) | listに指定された変数はprivate 指示節と同様、プライベートであることを宣言します。処理終了後の値を指定します |
shared(list) | listに指定された変数が各スレッドで共有変数であることを宣言します |
default(shared | none) | 並列リージョン内のすべての変数に対してデフォルトの属性が与えられます |
reduction(operator:list) | listで指定した変数に対して演算子operator のプライベートコピーが作成され、その値で更新されます |
copyin(list) | threadprivate指示文で指定された変数にコピーされます |
copyprivate(list) | 「#pragma omp single」指示文だけで利用できる指示節です。list に指定された変数をプライベートで使用するために、ほかのスレッドにコピーされます |
(2)そのほかの指示節
ordered | forループ中に「#pragma omp ordered」指示文が含まれていることを宣言します |
---|---|
schedule | forループにおいてループ反復のスレッド割り当てスケジュールを指定します |
if | 「#pragma omp parallel」指示文を並列実行する場合の条件を記述します |
num_threads | 実行スレッド数を指定します。環境変数OMP_NUM_THREADS よりも優先されます |
nowait | 処理が終了したスレッドはほかのスレッドの状況に関係なく、次の処理が実行されることを宣言します |
OpenMPでは指示文以外にも役に立つ実行時ライブラリ関数が提供されています。これらの関数を一切使わなくても並列化は行えますが、より高度な並列化を行う際に利用します。実行時ライブラリ関数を利用する場合には、プログラムの先頭部分に「#include 」を記述してOpenMP 用のヘッダファイルomp.h を読み込む必要があります。
(1)実行環境ルーチン
omp_set_num_threads(int) | 次の並列リージョン開始時に起動されるスレッド数を指定します |
---|---|
int omp_get_num_threads(void); | 現在、起動しているスレッドの数を戻り値で取得します |
omp_get_max_threads() | スレッドの最大生成数を戻り値で取得します |
int omp_get_thread_num(void); | 自分のスレッド番号を戻り値で取得します。(スレッド番号は0 からp-1(スレッド数をp とする)の値になります) |
omp_get_num_procs() | プログラムで使用可能なプロセッサの数を戻り値で取得します |
omp_in_parallel() | 並列起動の状態を戻り値で取得します(並列状態の場合は、0 以外の値となり、並列状態でない場合は、0 となります) |
omp_set_dynamic(int) | スレッド数の動的調整機能を指定します(有効にする場合は0 以外の値、無効にする場合は0) |
omp_get_dynamic() | スレッド数の動的調整機能の有効/無効を戻り値で取得します(有効である場合は0 以外の値、無効である場合には0) |
omp_set_nested(int) | 並列のネストを有効/無効を指定します(有効にする場合は0 以外の値、無効にする場合は0) |
omp_get_nested() | 並列のネストの有効/無効を戻り値で取得します(有効である場合は0 以外の値、無効である場合には0) |
(2)時間計測ルーチン
omp_get_wtime() | 1970年1月1日午前0時からの経過秒数、あるいは、システムを起動してからの経過秒数(倍精度実数)を戻り値で取得します |
---|---|
omp_get_wtick() | omp_get_wtime()の返す値の刻み幅(倍精度実数)を戻り値で取得します |
(3)ロックルーチン
void omp_init_lock(omp_lock_t *lock); | ロック変数を初期化します。lock は「omp_lock_t lock」のように宣言された変数です |
---|---|
void omp_destroy_lock(omp_lock_t *lock); | ロック変数を破棄します |
void omp_set_lock(omp_lock_t *lock); | ロックの所有権を得るまで待機し、ロックの所有権を得ると処理が戻ります |
void omp_unset_lock(omp_lock_t *lock); | ロックの所有権を解放します。これによりほかのスレッドがロックの所有権を得ることが可能となります |
int omp_test_lock(omp_lock_t *lock); | ロックの所有権を得ることを試みます(ロックの所有権を得ると1を返し、そうでない場合には0)。ただし、omp_set_lock()と違い、ロックの所有権が得られるまで待機しません |
(4)ネスト可能なロックルーチン
omp_init_nest_lock(omp_nest_lock_t *) | ネスト可能なロック変数を初期化します |
---|---|
omp_destroy_nest_lock(omp_nest_lock_t *) | ネスト可能なロック変数を破棄します |
omp_set_nest_lock(omp_nest_lock_t *) | すでに同じスレッドによって所有権を獲得している場合にはネストカウンタを1つ増やして処理が戻ります |
omp_unset_nest_lock(omp_nest_lock_t *) | ネスト可能なロックのネストカウンタを1つ減らします |
omp_test_nest_lock(omp_nest_lock_t *) | すでに同じスレッドによって所有権を獲得している場合にはネストカウンタを1つ増やしてその値を返します。そうでない場合には、ネスト可能なロックの所有権を得ることを試みます |
(5)OpenMP のデータ型(OpenMPで定義されているデータ型は下記の2つです)
omp_lock_t | ロック情報を格納する型(ロックルーチンで使用) |
---|---|
omp_nest_lock_t | ロック情報を格納する型(ネスト可能なロックルーチンで使用) |
環境変数はすべて大文字ですが、環境変数に設定する値については大文字と小文字の区別はありませんので、どちらで指定しても構いません。C/C++言語には大文字小文字の区別がありますが、OpenMPには大文字小文字の区別はありませんので注意しましょう。
OMP_NUM_THREADS | 使用するスレッド数を指定します |
---|---|
OMP_SCHEDULE | 「#pragma omp for」指示文と「#pragma omp parallel for」指示文においてschedule(runtime)指示節を指定した場合のスケジューリング方法を指定します |
OMP_DYNAMIC | スレッド数の動的調整機能(システムの負荷によって実行スレッド数を変更する機能)を有効にする場合はTRUE、無効にする場合はFALSE を指定します |
OMP_MESTED | 並列のネストを有効にする場合はTRUE、無効にする場合はFALSE を指定します |
OMP_WAIT_POLICY | スレッドの待機中の挙動を指定します。スピンループして待機する場合にはACTIVE(デフォルト)、スリープして待機する場合にはPASSIVE を指定します |
OMP_STACK_SIZE | 各スレッドが生成されるときのスタックサイズを指定します |
OpenMPはもともと、組み込み開発を想定された仕様ではありません。組み込み開発には、低消費電力制御、どのメモリーを使用するか、DMAコントローラを使用したデータ転送など、固有の制御が必要となります。そこで、組み込み開発向けの仕様として「OSCAR(Optimally Scheduled Advanced Multiprocessor)API」が開発されました。
OSCAR APIは、早稲田大学理工学術院教授の笠原 博徳氏と、半導体メーカーの、東芝、NEC、パナソニック、日立製作所、富士通研究所、ルネサス テクノロジらが参画して標準化したものです。
OSCAR APIは、低消費電力制御/メモリー配置/データ転送などの最適化を行う15種の指示文のみからなるコンパクトな仕様となっています。なお、OSCAR API(Version 1.0)の仕様書は、http://www.kasahara.cs.waseda.ac.jp/api/regist.htmlからダウンロード可能となっています。
今回は、OpenMPを使用して、これまで紹介してきた並列化手法を並列化する手法を紹介しました。将来ハードウェアがマルチコアになることは必然となっており、ソフトウェアにおいても急速にマルチコアへの対応が求められています。そして、マルチコア対応については、日々進化し続けています。
今回で連載は終了しますが、今後も将来に向けて、一緒に「マルチコア進化論」について考えていきましょう。ご意見、ご質問がある方は件名を「マルチコア並列化手法の件」として、メールアドレスinfo@zipc.comまでご連絡ください。また、参考ホームページはhttp://www.zipc.com/support/rre/multicore/になります。よろしくお願いいたします。
Copyright © ITmedia, Inc. All Rights Reserved.