3556＜チュートリアル＞
科 学 技 術 計 算 の 分 野 で 始 ま っ た GP U ( Gr a p h i c P r o c e s s i n g U n i t ) に よ る 汎 用 計 算 は 、製 造 業 、医 療 、

科学技術計算の分野で始まった GPU（Graphic Processing Unit）による汎用計算は、製造業、医療、金融など様々な分野へ広まり、モバイル、組み込みの分野でも使われ始めています。
用 言 語 を 使 う 必 要 が あ り GP U に よ る 汎 用 計 算 は 気 軽 に 始 め ら れ る も の で は あ り ま せ ん で し た
チュートリアル
当初は専用言語を使う必要があり GPU による汎用計算は気軽に始められるものではありまが 、 用 途 が 広 が る と 同 時 に 、 従 来 の 言 語 で GP U 向 け に プ ロ グ ラ ミ ン グ す る 環 境 が 整 っ て き ませんでしたが、用途が広がると同時に、従来の言語で GPU 向けにプログラミングする環境し た 。 OpenACC は そ の 中 の 一 つ で 、 既 存 の プ ロ グ ラ ム に デ ィ レ ク テ ィ ブ と 呼 ば れ る ヒ ン ト 情が整ってきました。OpenACC はその中の一つで、既存のプログラムにディレクティブと呼報 を 追 加 す る だ け で 、 コ ン パ イ ラ が 自 動 で GP U コ ー ド を 生 成 す る 開 発 手 法 で す 。 今 回 の チ ュ
ばれるヒント情報を追加するだけで、コンパイラが自動で GPU コードを生成する開発手法
ー ト リ ア ル で は 、 全 ４ 回 の シ リ ー ズ と し て 、 こ の OpenACC を 使 っ て ア プ リ ケ ー シ ョ ン を 効 率です。今回のチュートリアルでは、全4回のシリーズとして、この OpenACC を使ってアプリ
良 く GP U で 加 速 す る 方 法 を 解 説 し ま す 。
ケーションを効率良く GPU で加速する方法を解説します。
金 融 な ど 様 々 な 分 野 へ 広 ま り 、モ バ イ ル 、組 み 込 み の 分 野 で も 使 わ れ 始 め て い ま す 。当 初 は 専

OpenACC
で始めるGPUGPU コンピューティング:
OpenACC
で始める
コンピューティング：
ループの最適化
ループの最適化
Introduction of GPU computing by OpenACC: Optimize Loops
成瀬
1

はじめに
はじめに
前回までに、我々が推奨する OpenACC によるアプ
前回までに、我々が推奨する OpenACC によるアプリ
リケーション並列化サイクル（図 1）の内、「データ転
ケーション並列化サイクル（図1）の内、「データ転送の送の最適化」までの３ステップを説明した。例題とし最適化」
までの3ステップを説明した。例題として用いて用いているヤコビ反復法は、この３ステップを適用ているヤコビ反復法は、この3ステップを適用することすることで、GPU 上で相応の性能が得られるようになで、GPUった。 上で相応の性能が得られるようになった。

彰意が必要だ。

1

図1

成瀬 彰

2

図 1 OpenACC
によるアプリケーション
OpenACCによるアプリケーション並列化のサイクル
並列化のサイクル

今回は、OpenACC によるアプリケーション並列化の
今回は、OpenACC によるアプリケーション並列化
サイクルの最後のステップ、「ループの最適化」を説明のサイクルの最後のステップ、「ループの最適化」を説してゆく。
明してゆく。
2
ループの最適化
筆者紹介
データ転送の最適化の次は、ループの最適化である。
例えば、ループネストの各ループを、どのように対象なるせ あきらハードウェアにマッピングするかを調整したり、より
1996年、名古屋大学大学院工学系研究科修士課程
多くの並列性を抽出できるようにコード再構成するこ修了（情報工学専攻）。同年、富士通研究所に入社、とで、GPU 上の計算時間を更に短縮することができる。
大規模サーバ開発、HPC システム開発など、様々なプロジェクトに参加、関連するハードウェアや
ただ、ループ最適化は、対象プロセッサに特化した最ソフトウェアの研究開発に従事。2009年、山下記
適化になりやすい。性能ポータビリティの低下には注

ループの最適化

データ転送の最適化の次は、ループの最適化であ
2.1 配列のレイアウトと多重ループの順番る。例えば、ループネストの各ループを、どのようにOpenACC のディレクティブを最適化すると同時に、対象ハードウェアにマッピングするかを調整したり、多重ループの順番、もしくは、配列のデータレイアウ
より多くの並列性を抽出できるようにコード再構成す
トが不適切な場合は、それを正す必要がある（一種のることで、GPU
上の計算時間を更に短縮することがで
ソースコードの再構成）。CPU も GPU も、いわゆるスきる。ただ、ループ最適化は、対象プロセッサに特化
トライド・メモリアクセスは得意ではない。多次元配した最適化になりやすい。性能ポータビリティの低下
列の計算は、多重ループで表現されることが多いが、には注意が必要だ。 アクセスされる配列要素が、その多重ループの順番は、アクセス順でメモリアドレス上連続となるのがベストである。さもないと、デバイスメモリへのアクセス回
2.1 配列のレイアウトと多重ループの順番数が増える、
キャッシュメモリの利用効率が低下する、
OpenACCのディレクティブを最適化すると同時に、といった性能上の悪影響が生じる。図2 に、Fortran
多重ループの順番、もしくは、配列のデータレイアウと トが不適切な場合は、それを正す必要がある
C/C++それぞれの、多重ループの順番の良い例と悪（一種のい例を示す。
ソースコードの再構成）。CPU も GPU も、いわゆるス多重ループ内に複数の配列があり、配列によりデー
トライド・メモリアクセスは得意ではない。多次元配
タレイアウトが異なる場合は、各々の配列に関して、列の計算は、多重ループで表現されることが多いが、
図 2 の条件を満たすように、そのデータレイアウトを
その多重ループの順番は、アクセスさ れる配列要素
変更するのが理想である。しかし、ある多重ループ向が、アクセス順でメモリアドレス上連続となるのがベけに配列のデータレイアウトを最適化すると、その副ストである。さもないと、デバイスメモリへのアクセ作用が他の多重ループに及ぶこともあるだろう。現実
ス回数が増える、キャッシュメモリの利用効率が低下
的な妥協解は、
多重ループの順番を、Write される配列
する、 と い っ た 性 能 上 の 悪 影 響 が 生 じ る。 図2に、に適切な順番に変更することである。一般的に、CPU
とRead
C/C++
それぞれの、多重ループの順番の良いも Fortran
GPU も、に比べてWrite が遅い。遅い処理が、例と悪い例を示す。
更に遅くなるのを避けるという考え方である。
続きは Web で
日本計算工学会誌「計算工学（Vol.22, No.1）」HP:
http://www.jsces.org/Issue/Journal/

念研究賞受賞。2010年、SACSIS2010最優秀論文賞

受 賞。2013年、NVIDIA に 参 加、 シ ニ ア デ ベ ロ ッ
パーテクノロジーエンジニアとして様々なアルゴ
リズムとアプリケーションの GPU 向け並列化に従事、GPU コンピューティングの普及に努めている。

（24）

計算工学

チュートリアル

OpenACC で始める GPU コンピューティング : ループの最適化

多重ループ内に複数の配列があり、配列によりデー
タレイアウトが異なる場合は、各々の配列に関して、
図2の条件を満たすように、そのデータレイアウトを変更するのが理想である。しかし、ある多重ループ向けに配列のデータレイアウトを最適化すると、その副作用が他の多重ループに及ぶこともあるだろう。現実的な妥協解は、多重ループの順番を、Write される配列に適切な順番に変更することである。一般的に、CPU も
GPU も、Read に比べて Write が遅い。遅い処理が、更に遅くなるのを避けるという考え方である。

ループ）に対応する。Worker 並列は、ワープを意識した最適化を OpenACC で記述するときに必要になるが、多くの場合は Gang 並列と Vector 並列の2レベルの並列性で十分である。ループネスト等の処理に対して、
Gang 並列と Vector 並列を適用したときのイメージを図3に示す。

図 3 Gang
Gang並列と並列とVector
Vector 並列による処理分割
並列による処理分割
図3

2.3

図2

2.3

並列性の粒度の指定（Gang/Worker/Vector ク

並列
性 の 粒 度クローズ）の 指 定（Gang/Worker/Vector ク
ローズ、Seq
ローズ、Seq
クローズ）
多重ループの各ループを、どの粒度で並列化するかの指定には、主に、Gang クローズと Vector クローズ
多重ループの各ループを、どの粒度で並列化するかを使用する。これらディレクティブは、Loopの指定には、主に、Gang
クローズと Vectorコンスト
クローズを
ラクトのオプションであり、Gang クローズを指定した使用する。これらディレクティブは、Loop コンストラ
図 2 多重ループの順番
ループには Gang 並列が適用され、Vector クローズをクトのオプションであり、Gang
クローズを指定した（上:悪い例、下:良い例）
指定したループには Vector 並列が適用される。もちろ
ループには
Gang
並列が適用され、Vector
クローズを指ん、Worker
並列に対応する
Worker クローズも存在す
2.2 3 つの粒度の並列性る。なお、あるループを並列化せずに、逐次的に実行定したループにはVector 並列が適用される。もちろん、OpenACC では、3 つの粒度の並列性、Gang 並列、Worker
させるときにはSeq クローズを使用する。
並列に対応する
Worker クローズも存在する。なWorker 並列、Vector 並列が定義されている。Gang 並列
Gang/Vector クローズの使い方は、Parallel コンスト
お、あるループを並列化せずに、逐次的に実行させるが最も粒度の粗い並列性、Vector 並列が最も細かい並ラクトと Kernels コンストラクトで違いがあるので、Seq クローズを使用する。
列性に対応しており、Worker 並列はその中間の粒度でときには以下、それぞれの場合の使用方法を説明する。
ある。この 3 種類の並列性が、具体的にハードウェア Gang/Vector クローズの使い方は、Parallel コンストラ
Parallel
コンストラクトの場合:のどの部分に対応するのかは、対象プロセッサ（GPUクトとKernels
コンストラクトで違いがあるので、以図 4 に、Parallel コンストラクトを使用して、2 重or CPU）によって変わる。GPU の場合、細粒度な Vector下、それぞれの場合の使用方法を説明する。

多重ループの順番（上：悪い例、下：良い例）
ループの外ループに Gang 並列を、内ループに Vector
並列で分割されたループの各イタレーションは、ある並列を明示的に適用するときの記述例を示す。Parallel
一つの GPU スレッドに割当てられる。粗粒度な Gang
Parallel
コンストラクトの場合：
コ ン ス ト ラ ク ト 内 の Loop コ ン ス ト ラ ク ト に並列で分割されたループの各イタレーションは、ある
2.2 3つの粒度の並列性
図4に、Parallel
コンストラクトを使用して、2重ルー
Gang/Vector クローズを付けないときは、
コンパイラが1024 並個のGPU スレOpenACC で 一つのスレッドブロック[1]（最大は、3つ の 粒 度 の 並 列 性、Gang列、各ループに適用する並列性の粒度を決定するが、ッドのグループ）に割当てられる。Worker 並列は、 プの外ループにGang 並列を、内ループに Vector 並列をWorker 並列、Vector 並列が定義されている。Gang 並列
Gang/Vector クローズの指示があるときは、コンパイラ
CUDA で言うところのワープ[1]（32 個の GPU スレッ明示的に適用するときの記述例を示す。Parallelコンスが最も粒度の粗い並列性、Vector
並列が最も細かい並
つの Parallel コンストラク
ドのグループ）に対応する。Worker
並列は、ワープを
ト ラはその指示に従う。なお、1
ク ト 内 の Loop コ ン ス ト
ラ ク ト に Gang/Vector ク
列性に対応しており、Worker
ト内で、Gang クローズを付けられるループは一つだけ意識した最適化を並列はその中間の粒度でOpenACC で記述するときに必要にローズを付けないときは、コンパイラが各ループに適である。Vector クローズも同様である。また、Parallel
なるが、多くの場合は Gang 並列と Vector 並列の 2 レある。この3種類の並列性が、具体的にハードウェアの用する並列性の粒度を決定するが、Gang/Vector
クロー
コンストラクトでは、ある特定のループに対して複数ベルの並列性で十分である。ループネスト等の処理にどの部分に対応するのかは、対象プロセッサ（GPU orの並列性の粒度、例えば、Gang クローズと Vector ク
対して、Gang 並列と Vector 並列を適用したときのイ ズの指示があるときは、コンパイラはその指示に従CPU）によって変わる。GPU の場合、細粒度な Vector 並
ローズの両方を付けることはできない。
メージを図 3 に示す。
う。なお、1つのParallel コンストラクト内で、Gang ク

列で分割されたループの各イタレーションは、ある一
つの GPU スレッドに割当てられる。粗粒度な Gang 並列で分割されたループの各イタレーションは、ある一つのスレッドブロック [1]（最大1024個の GPU スレッドのグループ）に割当てられる。Worker 並列は、CUDA
で 言 う と こ ろ の ワ ー プ [1]（32個 の GPU ス レ ッ ド の グ
計算工学

ローズを付けられるループは一つだけである。Vector
クローズも同様である。また、Parallel コンストラクト
では、ある特定のループに対して複数の並列性の粒度、例えば、Gang クローズと Vector クローズの両方を付けることはできない。

（24-2）

Vol.22, No.1 2017

チュートリアル

OpenACC で始める GPU コンピューティング : ループの最適化

図 4 Parallel コンストラクトのGang/Vector
クローズの使用例
図 4 Parallel
コンストラクトのクローズの使用例
図4 ParallelコンストラクトのGang/Vector
クローズの使用例
図 4 Gang/Vector
Parallel コンストラクトの

図 5
Parallel コンストラクトでの図 コンストラクトでの5
Parallel
コンストラクトでのGang
数と Vector数の設定例
図5 Parallel
Gang
数と Vector 数の設定例

数と Vector 数の設定例
図 4 の状態では、依然として、何個のGang（スレ
Gang/Vector クローズの使用例
図 5GangParallel
コンストラクトでの
図4の状態では、依然として、何個のGang（スレッドブロック）が、そして、一つの
Gang
に対して何
Kernels
Kernelsコンストラクトの場合:
コンストラクトの場合：
図4の状態では、依然として、何個の
Gang
（スレッド
Gang 数と Vector 数の設定例
ッドブロック）が、そして、一つの Gang に対して何
Kernels
コンストラクトの場合:
Kernels
コンストラクトの場合のGang/Vector
クロー個のVector（スレッド）が生成されるかは、コンパイ
Kernels コンストラクトの場合のGang/Vector
クローズ
ブロ
ッ
ク）が、そ し て、一 つ の Gang に 対して 何個 の
図
4 の状態では、依然として、何個のGang（スレ個のVector（スレッド）が生成されるかは、コンパイ
Kernels
コンストラクトの場合のGang/Vector
クローズの使用例を、図6に示す。Parallel
コンストラクトラの判断に委ねられているが、OpenACC
には
Gang
数
の使用例を、図6に示す。Parallel
コンストラクトと同Vector（スレッド）が生成されるかは、コンパイラの判
ッドブロック）が、そして、一つのGang に対して何
Kernels
コンストラクトの場合:
ラの判断に委ねられているが、OpenACCには Gang 数
ズの使用例を、図6 に示す。Parallel コンストラクトと同様、Gang/Vector
クローズは、Loop
コンストラクや個のVector数を指定するディレクティブも用意されてい様、Gang/Vector
クローズは、Loop
コンストラクトのオ
断に委ねられているが、OpenACC
には
Gang
数や
VectorKernels コンストラクトの場合のGang/Vector
クロー
Vector（スレッド）が生成されるかは、コンパイ
や
Vector 数を指定するディレクティブも用意されていと同様、Gang/Vector
クローズは、Loop
コンストラクプションとして追加すればよい。Parallel
コンストラクトのオプションとして追加すればよい。
Parallel
コンス
る。
Parallel
コンストラクトでは、
Num_gangs
クローズ
数
を
指
定
す
る
デ
ィ
レ
ク
テ
ィ
ブ
も
用
意
さ
れ
て
い
る。
ズの使用例を、図6に示す。Parallel
コンストラクトラの判断に委ねられているが、OpenACC
には
Gang
数
る。Parallel コンストラクトでは、Num_gangs クローズ
トのオプションとして追加すればよい。Parallel コンストとの違いは、Kernels
コンストラクトで生成した領域
コンスト
ラ ク ト で は、Num_gangs ク ロ ー数とズととやParallel
Vector_length
クローズが、それぞれ、Gangs
トラクトとの違いは、Kernels
コンストラクトで生成しVector数を指定するディレクティブも用意されていと同様、Gang/Vector
クローズは、Loop
コンストラクと Vector_length
クローズが、それぞれ、Gangs 数とトラクトとの違いは、Kernels
コンストラクトで生成し内では、ある多重ループを構成する複数のループに対Vector_length
クローズが、それぞれ、Gangs
数とVectorた領域内では、ある多重ループを構成する複数のルー
Vector数を指定するディレクティブである（Worker
数る。Parallel
コンストラクトでは、Num_gangs クローズ数
トのオプションとして追加すればよい。
Parallel コンス
Vector 数を指定するディレクティブである（Worker
た領域内では、ある多重ループを構成する複数のルー
して、Gang
クローズを付けられることである。同様（Worker
数を指定を指定するのは、
Num_workers
クローズ）。その使用例
クローズを付けられることである。
を指定するのは、
Num_workers
クローズ）
。
その使用例
プに対して、Gang
クローズを付けられることである。
と数を指定するディレクティブである
Vector_length
クローズが、それぞれ、Gangs
数と プに対して、Gang
トラクトとの違いは、Kernels コンストラクトで生成しに、Vector
クローズも複数のループに付けることがでするのは、Num_workers
クローズ）。その使用例を、図を、図5数を指定するディレクティブである（Workerに示す。この例では、Gang
数として
Nj（外
クローズも複数のループに付けること5 に示す。この例では、Gang
数として
Nj（外
同様に、Vector
クローズも複数のループに付けること
Vectorを、図数 同様に、Vector
た領域内では、ある多重ループを構成する複数のルー
きる。また、ある特定のループに対して、複数の並列5に示す。この例では、Gang
数として
Nj
（
外ループの反ができる。また、ある特定のループに対して、複数のループの反復回数）が指定されているので、Nj個のスループの反復回数）が指定されているので、Nj個のスができる。また、ある特定のループに対して、複数のプに対して、Gang クローズを付けられることである。
を指定するのは、Num_workers クローズ）。その使用例性、つまり、Gang
クローズとVector Vector
クローズの両方を
復 回レッドブロックが生成され、各スレッドブロックに対数）が 指 定 さ れ て い る の で、Nj 個 の ス レ ッ ド ブ
並列性、つまり、Gang
クローズと
クローズと
Vectorクローズのクローズのレッドブロックが生成され、各スレッドブロックに対同様に、Vector
クローズも複数のループに付けることを、図5 に示す。この例では、Gang 数として Nj（外 並列性、つまり、Gang
付けることもできる。結果として、Parallel
コンストラ
ロックが生成され、各スレッドブロックに対して、外して、外ループのイタレーションが一つ割当てられる。
両方を付けることもできる。結果として、Parallel
コン
コン
して、
外ループのイタレーションが一つ割当てられる。
ができる。また、ある特定のループに対して、複数のループの反復回数）が指定されているので、Nj
個のス 両方を付けることもできる。結果として、Parallel
クトよりも、Kernels
コンストラクトの方が並列性を記
ループのイタレーションが一つ割当てられる。次に、次に、Vector数として256が指定されているので、ス
ストラクトよりも、
Kernels
コンストラクトの方が並列ストラクトよりも、
Kernels
コンストラクトの方が並列
次に、Vector数として256が指定されているので、ス
並列性、つまり、Gang クローズと Vector クローズのレッドブロックが生成され、各スレッドブロックに対述する自由度が高いという状況になっている。
Vector数として256が指定されているので、スレッドブ
レッドブロックあたり
256 個のスレッドが生成され、 性を記述する自由度が高いという状況になっている。
性を記述する自由度が高いという状況になっている。
レッドブロックあたり
256 個のスレッドが生成され、両方を付けることもできる。結果として、Parallel コン
して、
外ループのイタレーションが一つ割当てられる。
内ループの各イタレーションは、この 256 個のスレッ
ロックあたり256個のスレッドが生成され、内ループの
内ループの各イタレーションは、この256 個のスレッストラクトよりも、Kernels コンストラクトの方が並列
次に、Vector
数として 256 が指定されているので、スドのいずれかに割当てられる（PGI
コンパイラではサ
各イタレーションは、この256個のスレッドのいずれか
ドのいずれかに割当てられる（PGI
コンパイラではサレッドブロックあたり256個のスレッドが生成され、性を記述する自由度が高いという状況になっている。
イクリック割当てが行われる。具体的には、図5 の場に割当てられる（PGI コンパイラではサイクリック割当
イクリック割当てが行われる。具体的には、図5 の場
内ループの各イタレーションは、この
256
個のスレッ
合、N 番目のスレッドには、N 番目、N+256 番目のス
番目、N+512てが行われる。具体的には、図5の場合、N合、N 番目のスレッドには、
N 番目、N+256
番目、N+512 。
ドのいずれかに割当てられる（PGI
コンパイラではサ番目、…などの複数のイタレーション割当てられる）レッドには、N 番目、N+256番目、N+512番目、…など
番目、…などの複数のイタレーション割当てられる）。
なお、
Num_gangs クローズと Vector_length
クローズは、
イクリック割当てが行われる。具体的には、図5 の場の複数のイタレーション割当てられる）。なお、Num_
なお、
Num_gangs
クローズと Vector_length
クローズは、
Loop
コンストラクトのオプションではない。Parallel合、N
番目のスレッドには、N クローズは、Loop
番目、N+256
番目、
N+512
gangs
クローズと
Vector_length
コンス
Loop
コンストラクトのオプションではない。Parallel
コンストラクトのオプションなので、記述場所は番目、…などの複数のイタレーション割当てられる）
トラクトのオプションではない。Parallel コンストラク 。
Parallel
コンストラクトの行になる。
コンストラクトのオプションなので、記述場所は
なお、
Num_gangs
クローズと Vector_length
トのオプションなので、記述場所はParallelクローズは、
コンストラ
Parallel
コンストラクトの行になる。
Loop
コンストラクトのオプションではない。Parallel
クトの行になる。

コンストラクトのオプションなので、記述場所はParallel コンストラクトの行になる。

図 6

Kernels コンストラクトの Gang/Vector クローズ

コンストラクトのGang/Vector
クローズの使用例
図図6
6 Kernels
Kernels
コンストラクトのGang/Vector
クローズ

計算工学

（24-3）図 6

Kernels コンストラクトの Gang/Vector
Vol.22, No.1 クローズ2017

チュートリアル

ない。GPUは 32 GPU
スレッドが同期的に動くので、Vector
OpenACC
で始める
コンピューティング : ループの最適化の使用例

数は最低でも 32 必要で、可能なら 128 以上が望ましい。
の使用例ない。GPU は 32 スレッドが同期的に動くので、Vector
Kernels コンストラクトで Gang 数と Vector 数を指定
また、Parallel コンストラクトでは、Gang/Worker/Vector
数は最低でも32 必要で、可能なら 128 以上が望ましい。
ローズは、それぞれ一つのループにしか付けられない
Kernels コンストラクトでGang
数と Vector 数を指定すする方法は、Parallel
コンストラクトとは異なる。
クローズは、それぞれ一つのループにしか付けられなKernels
コンストラクトでGang
数とVector
数を指定
また、
Parallel
コンストラクトでは、Gang/Worker/Vector
る方法は、Parallel
コンストラクトとは異なる。Parallel
Parallel コンストラクトでは、Num_gangs
クローズや ので、4重ループ以上のループネストでは、並列性を付いので、4 重ループ以上のループネストでは、並列性する方法は、Parallel
コンストラクトとは異なる。
クローズは、それぞれ一つのループにしか付けられな
コ ン ス トVector_length
ラ ク ト で は、Num_gangs
クローズ
や Vector_
クローズを使用したが、Kernels
コンスト けられないループが出てくる。
を付けられないループが出てくる。
Parallel
コンストラクトでは、Num_gangs
クローズやいので、4 重ループ以上のループネストでは、並列性
ラクトでは、Gang/Vector クローズの後に括弧付きで値
lengthクローズを使用したが、Kernels
コンストラクト
Vector_length
クローズを使用したが、Kernels コンストを記述すれば、それが、Gang
数・Vector
数の指定値とを付けられないループが出てくる。
では、Gang/Vector
クローズの後に括弧付きで値を記述
ラクトでは、
Gang/Vector
クローズの後に括弧付きで値なる。図7にKernels
コンストラクトでのGang 数・すれば、それが、Gang 数・Vector 数の指定値となる。
を記述すれば、それが、Gang
数・Vector 数の指定値とVector 数の指定例を示す。

図7に Kernels コンストラクトでの Gang 数・Vector 数のなる。図 7 に Kernels コンストラクトでの Gang 数・指定例を示す。
Vector 数の指定例を示す。

図 7
Kernels コンストラクトでのGang 数と Vector 数の設定例

図図88Collapseクローズの使用例
クローズの使用例
Collapse

Parallel コンストラクトと Kernels コンストラクトでこのような場合に有用なのが、Collapseクローズであクローズでこのような場合に有用なのが、Collapse
注意すべきもう一つの違いは、Kernels コンストラクト
ある。Collapse クローズは Loop コンストラクトのオプる。Collapse
ー ズ はクローズの使用例
Loop コ ン ス ト ラ ク ト の オ プ
Gang 数と Vector 数の設定例
図 8 クロCollapse
内での指示は、あくまでコンパイラに対するヒント、ションで、複数ループを束ねて、反復回数の多い一つションで、複数ループを束ねて、反復回数の多い一つ
Parallel コンストラクトとKernels コンストラクトで注ということである。例えば、図7 の例では、外ループのループに融合するディレクティブである。束ねるル
Parallelはコンストラクトとコンストラクトでこのような場合に有用なのが、Collapse
クローズで意すべきもう一つの違いは、Kernels
コンストラクト内
Gang 並列で Gang Kernels
数として
Nj を指定、内ループは のループに融合するディレクティブである。束ねる
ープ数は、Collapse の後の括弧内に数値として指定す
注意すべきもう一つの違いは、Kernels256
コンストラクトCollapse
クローズはLoop コンストラクトのオプ
ループ数は、Collapse
Vector 並列で Vector 数としてを指定しているが、ある。
る。図8にCollapse の後の括弧内に数値として指定す
クローズの使用例を示す。この例
での指示は、あくまでコンパイラに対するヒント、と内での指示は、あくまでコンパイラに対するヒント、ションで、複数ループを束ねて、反復回数の多い一つる。図8にCollapse
クローズの使用例を示す。この例でコンパイラはこの指示に従わないかもしれない。コンでは、4重ループの外側の2 ループを Collapse クロー
いうことである。例えば、図7の例では、外ループはということである。例えば、図7 の例では、外ループのループに融合するディレクティブである。束ねるル
パイラは、例えば、内ループに対して
Gang
並列とズで融合して
Gang
並列を適用、内側のループもは、4重ループの外側の2ループをCollapse2 クローズでGang 並 列 で Gang 数 と し て Nj を 指 定、 内 ル ー プ はVector 並列の両方を適用したり、スレッド数として 128ープ数は、Collapse
CollapseGang
クローズで融合してVector 並列を適用していは Gang 並列でGang 数として Nj を指定、内ループはの後の括弧内に数値として指定す
融合して並列を適用、内側の2ループもCollapse
Vector 並列で Vector 数として256を指定しているが、コを選択するかもしれない。PGI
コンパイラの場合、コる。図る。これ以外にもループの融合方法はいくつか考えら
Vector 並列でVector 数として 256 を指定しているが、8にCollapse
クローズの使用例を示す。この例
クローズで融合して Vector 並列を適用している。これ
ンパイラはこの指示に従わないかもしれない。コンパ
ンパイラのフィードバックメッセージを見ると、コンでは、4れるが、いろいろなループ融合方法を簡単に試せるのコンパイラはこの指示に従わないかもしれない。コン重ループの外側の 2 ループを Collapse クロー
以外にもループの融合方法はいくつか考えられるが、イラは、例えば、内ループに対してGang 並列とVector
パイラの選択結果を確認できるので、Kernels
コンストズで融合しても CollapseGang
クローズのメリットである。
パイラは、例えば、内ループに対してGang
並列と並列を適用、内側の 2 ループもいろいろなループ融合方法を簡単に試せるのもCollapse
並列の両方を適用したり、スレッド数として128を選択
ラクトの場合は特にコンパイラのフィードバックメッ
Vector 並列の両方を適用したり、スレッド数として 128
Collapse
クローズで融合してVector 並列を適用していクローズのメリットである。
するかもしれない。PGI
コンパイラの場合、コンパイ
セージを注意して見て欲しい。
ただし、ループ間に何らかの処理が入っているとこを選択するかもしれない。PGI
コンパイラの場合、コる。これ以外にもループの融合方法はいくつか考えら
ただし、ループ間に何らかの処理が入っているとこ
ろには、そのままでは Collapse クローズを適用できな
ラのフィードバックメッセージを見ると、コンパイラ
ンパイラのフィードバックメッセージを見ると、コン
れるが、いろいろなループ融合方法を簡単に試せるのろには、そのままではCollapse クローズを適用できな
2.4 Collapse クローズ
い。Collapse クローズは、融合されるループが連続しの選択結果を確認できるので、Kernels
コンストラクトパイラの選択結果を確認できるので、Kernels
コンスト
も Collapse クローズのメリットである。
科学技術計算では、3重ループ、4重ループ、それていることが適用条件となっているので、ループ間にい。Collapse
クローズは、融合されるループが連続しての場合は特にコンパイラのフィードバックメッセージ
ラクトの場合は特にコンパイラのフィードバックメッ
以上のループで構成される多重ループも珍しくない。 いることが適用条件となっているので、ループ間に処処理が入っている場合は、その処理を他の場所に移動を注意して見て欲しい。
セージを注意して見て欲しい。
ただし、ループ間に何らかの処理が入っているとこ
そのような場合、各ループに対してどのように並列性 理が入っている場合は、その処理を他の場所に移動すする必要がある。また、内ループの反復回数が、外ル
ろには、そのままではCollapse クローズを適用できなを指示すればよいだろう。Vector 並列を最内ループにープのイタレーション毎に変わる場合も、そのままでる必要がある。また、内ループの反復回数が、外ルー
2.4
クローズ
2.4 Collapse
Collapse
クローズ
い。Collapse
クローズは、融合されるループが連続し割当てるのが基本だが、もし、最内ループの反復回数
は Collapse クローズを適用できない。その場合は、
図 9プのイタレーション毎に変わる場合も、そのままでは
科学技術計算では、3重ループ、4重ループ、それ以科学技術計算では、3重ループ、4 重ループ、それが非常に少ないと、単に最内ループにVector 並列を適ていることが適用条件となっているので、ループ間にに示すようにコードを変更すると、具体的には、内ルCollapse
クローズを適用できない。その場合は、図9に上のループで構成される多重ループも珍しくない。そ以上のループで構成される多重ループも珍しくない。
用するだけでは GPU の計算リソースを効率良く使え処理が入っている場合は、その処理を他の場所に移動
ープの反復回数を固定し、ループ内に条件判定を追加

図コンストラクトでの
7
Kernels コンストラクトでの
図7 Kernels
Gang 数と Vector 数の設定例のような場合、各ループに対してどのように並列性をそのような場合、各ループに対してどのように並列性を指示すればよいだろう。Vector
並列を最内ループに指示すればよいだろう。Vector
並列を最内ループに割割当てるのが基本だが、もし、最内ループの反復回数当てるのが基本だが、もし、最内ループの反復回数がが非常に少ないと、単に最内ループにVector
Vector
並列を適非常に少ないと、単に最内ループに並列を適用用するだけでは
GPUの計算リソースを効率良く使えするだけではGPUの計算リソースを効率良く使えな示すようにコードを変更すると、具体的には、内ルーする必要がある。また、内ループの反復回数が、外ルプの反復回数を固定し、ループ内に条件判定を追加すープのイタレーション毎に変わる場合も、そのままでると、Collapse
クローズを適用できるようになる。
は Collapse クローズを適用できない。
その場合は、
図 9に示すようにコードを変更すると、具体的には、内ループの反復回数を固定し、ループ内に条件判定を追加

い。GPU は32スレッドが同期的に動くので、Vector 数
は最低でも32必要で、可能なら128以上が望ましい。また、Parallel コンストラクトでは、Gang/Worker/Vector ク
計算工学（24-4）

Vol.22, No.1 2017

ある。図 10 に、Independent クローズの使用例を示す。
この例では、配列のインデックス（変数 ii）がテーブ
ル引きで決められているため、コンパイラは無条件に
は内ループを並列化できないが、Independent クローズ
OpenACC
で始める GPU コンピューティング : ループの最適化を使って、このループは並列化しても安全とコンパイある。図ラに通知できる。
10 に、Independent クローズの使用例を示す。
この例では、配列のインデックス（変数 ii）がテーブ
ル引きで決められているため、コンパイラは無条件に
は内ループを並列化できないが、Independent クローズを使って、このループは並列化しても安全とコンパイ
ラに通知できる。

すると、Collapse クローズを適用できるようになる。

チュートリアル

すると、Collapse クローズを適用できるようになる。

図図10
10 Independent
Independentクローズの使用例
クローズの使用例

データ競合が発生する等、並列化できないループに
データ競合が発生する等、並列化できないループにIndependent
クローズを付けると、当然、計算結果がおIndependent クローズを付けると、当然、計算結果がおかしくなることがあるが、それは開発者の責任である。
かしくなることがあるが、それは開発者の責任であ図 10
Independent クローズの使用例
なお、Independent クローズは Kernels コンストラクト
る。なお、Independent クローズは Kernels コンストラク
で使われるディレクティブである。Parallel コンストラ
データ競合が発生する等、並列化できないループに コンスト
トで使われるディレクティブである。Parallel
図 9
Collapse クローズ適用のためのクトでは、ループに対して Loop コンストラクトを付Independent
クローズを付けると、当然、計算結果がおラクトでは、ループに対してLoop コンストラクトを付
ソースコード修正例
けるだけで、そのループは並列化しても安全とコンパかしくなることがあるが、それは開発者の責任である。
けるだけで、そのループは並列化しても安全とコンパ
イラに通知したことになるので、Parallel コンストラク
なお、Independent クローズは Kernels コンストラクト
コンストラク
なお、図 8 と図 9 の例では、どちらも Parallel コン イラに通知したことになるので、Parallel
トでは Independent クローズを使う必要はない。
で使われるディレクティブである。Parallel コンストラ
ス ト ラ ク ト を 用 い て いる が 、 Collapse ク ロ ーズ は トでは Independent クローズを使う必要はない。
図 9
Collapse クローズ適用のためのクトでは、ループに対して Loop コンストラクトを付
2.6 Atomic コンストラクト
けるだけで、そのループは並列化しても安全とコンパ
10 の様なケースで、データ競合が発生する可能2.6 図
Atomic
コンストラクト
イラに通知したことになるので、Parallel コンストラク
なお、図8と図9の例では、どちらもParallel コンスト
2.5 Independent クローズ性はあるが、それでも、そのループを並列化したい場
図10の様なケースで、データ競合が発生する可能性
なお、図 8 と図 9 の例では、どちらも Parallel コン
トでは Independent
クローズを使う必要はない。
ラクトを用いているが、Collapse
クローズは Kernels コ
ループの各イタレーションでアクセスされる配列
合はどうすればよいか。このような場合に有用なディス ト ラ ク ト を 用 い て いる が 、 Collapse ク ロ ーズ ははあるが、それでも、そのループを並列化したい場合の場所（インデックス）は、ループ変数がそのまま、
レクティブが、Atomic コンストラクトである。図 11
ンストラクト内でも使用できる。
Kernels コンストラクト内でも使用できる。
2.6 はどうすればよいか。このような場合に有用なディレ
Atomic コンストラクト
もしくは、ループ変数に固定値を加減して決められるに、Atomic コンストラクトの使用例を示す。
図クティブが、Atomic
10 の様なケースで、データ競合が発生する可能
コンストラクトである。図11に、ことが多いが、テーブル引きで配列インデックスが決2.52.5Independent
クローズ
Independent
クローズ
性はあるが、それでも、そのループを並列化したい場
Atomic コンストラクトの使用例を示す。
められることもある。この場合の問題は、コンパイラ
ループの各イタレーションでアクセスされる配列
合はどうすればよいか。このような場合に有用なディループの各イタレーションでアクセスされる配列のがそのループを並列化できないことである。仮に、その場所（インデックス）は、ループ変数がそのまま、場所（インデックス）は、ループ変数がそのまま、もし レクティブが、Atomic コンストラクトである。図 11のテーブル内の値（配列のインデックス値）が互いにもしくは、ループ変数に固定値を加減して決められるに、Atomic コンストラクトの使用例を示す。
くは、ループ変数に固定値を加減して決められること異なり、配列へのアクセスで競合、つまり、配列の同ことが多いが、テーブル引きで配列インデックスが決が多いが、テーブル引きで配列インデックスが決めらじ場所が異なるイタレーションから更新されることがめられることもある。この場合の問題は、コンパイラれることもある。この場合の問題は、コンパイラがそ無いと開発者には分かっていても、コンパイラにはそがそのループを並列化できないことである。仮に、そのループを並列化できないことである。仮に、そのれが分からない。コンパイラは、データ競合が発生すのテーブル内の値（配列のインデックス値）が互いにる可能性のあるループを、無条件では並列化しない。
テーブル内の値（配列のインデックス値）が互いに異な異なり、配列へのアクセスで競合、つまり、配列の同
このような場合に有用なのが Independent クローズでり、配列へのアクセスで競合、つまり、配列の同じ場
じ場所が異なるイタレーションから更新されることが
図9

Collapse クローズ適用のためのソースコード修正例
Kernels コンストラクト内でも使用できる。
ソースコード修正例

無いと開発者には分かっていても、コンパイラにはそ所が異なるイタレーションから更新されることが無い
れが分からない。コンパイラは、データ競合が発生すと開発者には分かっていても、コンパイラにはそれがる可能性のあるループを、無条件では並列化しない。
分からない。コンパイラは、データ競合が発生する可このような場合に有用なのが Independent クローズで能性のあるループを、無条件では並列化しない。このような場合に有用なのが Independent クローズである。
図10に、Independent クローズの使用例を示す。この例
では、配列のインデックス（変数 ii）がテーブル引きで決められているため、コンパイラは無条件には内ルー
プ を 並 列 化 で き な い が、Independent ク ロ ー ズ を 使 って、このループは並列化しても安全とコンパイラに通
知できる。
計算工学

（24-5）

Vol.22, No.1 2017

た GeoFEM-Cube-OMP/CG である。このプログラムは、
並列有限要素法プラットフォーム GeoFEM[2]に基づOpenACC で始める GPU コンピューティング: ループの最適化
き、一様な物性を有する
Cube 形状を対象として三次元弾性静解析問題を解く場合の処理をベンチマーク化したものである。 である。このプログラムは、
た GeoFEM-Cube-OMP/CG
静解析問題を解く場合の処理をベンチマーク化したもこのプログラムでは、処理時間の大部分は
CG 法（共並列有限要素法プラットフォーム
GeoFEM[2]に基づ
のである。
役勾配法）
による連立一次方程式解法に費やされるが、き、一様な物性を有する
Cube
形状を対象として三次
このプログラムでは、処理時間の大部分は CG 法（共元弾性静解析問題を解く場合の処理をベンチマーク化
その中でも特に時間を要するのは、SpMV 計算（疎行役勾配法）による連立一次方程式解法に費やされるが、したものである。
列とベクトルの積）である。該当部分のソースコード
その中でも特に時間を要するのは、SpMV 計算（疎行列
このプログラムでは、処理時間の大部分は CG 法（共を図 12 に示す。なお、このコードは、説明簡略化のとベクトルの積）である。該当部分のソースコードを図
役勾配法）
による連立一次方程式解法に費やされるが、ため、元コードをかなり簡素化したものになっている
12に示す。
その中でも特に時間を要するのは、SpMV計算（疎行ことに注意して欲しい。

チュートリアル列とベクトルの積）である。該当部分のソースコード
を図 12 に示す。なお、このコードは、説明簡略化のため、元コードをかなり簡素化したものになっていることに注意して欲しい。

図 11

Atomic コンストラクトの使用例

この例では、ある配列要素が、複数の異なるイタレ
図11 Atomic
Atomicコンストラクトの使用例
コンストラクトの使用例
図11
ーションから更新される可能性があるため、単純に並図 12 GeoFEM-Cube-OMP/CG
GeoFEM-Cube-OMP/CG ののSpMV
計算コード
列化すると正しい計算結果が得られないことがある。
図12
SpMV
計算コード
この例では、ある配列要素が、複数の異なるイタレ
この例では、ある配列要素が、複数の異なるイタ
このような場合でも、配列 A へ加算処理を Atomic コーションから更新される可能性があるため、単純に並レーションから更新される可能性があるため、単純にこの SpMV
計算は、2 重ループで構成されている。
ンストラクトでガードすると、並列化しても安全に計
SpMV 計算は、2重ループで構成されており、
図 図12の12 GeoFEM-Cube-OMP/CGの SpMV 計算コード
列化すると正しい計算結果が得られないことがある。
外ループのイタレーション間には依存性がないので、算できるようになる。Atomic
コンストラクトでガード並列化すると正しい計算結果が得られないことがあ
このような場合でも、配列A へ加算処理を
Atomic コ
外ループは各節点（N：節点数）に関するループであ並列化が可能である。内ループ間のイタレーション間された部分は、複数スレッド間で競合が発生しなけれる。このような場合でも、配列A へ加算処理を Atomic
このSpMV 計算は、2 重ループで構成されている。
ンストラクトでガードすると、並列化しても安全に計り、内ループは各節点の非零非対角成分に関するルーには依存性があるが、その内容は縮約演算（VAL1,
ば、各スレッドの処理は並列に行われるが、スレッド
コンストラクトでガードすると、並列化しても安全に
外ループのイタレーション間には依存性がないので、算できるようになる。
Atomic コンストラクトでガードプである。N_ELEMENTS(i) は節点 i の非零非対角成分
VAL2, VAL3 に対する Reduction Sum）なので、こちら間で競合が発生すると（複数スレッドが配列の同じ場計算できるようになる。Atomic
コンストラクトでガー 並列化が可能である。内ループ間のイタレーション間された部分は、複数スレッド間で競合が発生しなけれの数を、IDX(j,i) は節点 i の j 個目の非零非対角成分に対も並列化が可能である。更に、これは図 12 からは読所を同時に更新しようとすると）、その複数スレッドのには依存性があるが、その内容は縮約演算（VAL1,
ば、各スレッドの処理は並列に行われるが、スレッド
ドされた部分は、複数スレッド間で競合が発生しなけ
応する節点番号を示している。Naïve な SpMV 計算と比VAL2,
VAL3 に対する Reduction Sum）なので、こちら間で競合が発生すると（複数スレッドが配列の同じ場み取れない情報だが、内ループのイタレーション回数
処理が順番に行われる。当然、競合が発生すると処理れば、各スレッドの処理は並列に行われるが、スレッ
べて計算内容が複雑なのは、三次元弾性静解析問題でも並列化が可能である。更に、これは図12 からは読所を同時に更新しようとすると）、その複数スレッドのは高々27回と少ないのに対して、外ループのイタレー
速度は低下するが、実際には競合が発生しない場合でド間で競合が発生すると（複数スレッドが配列の同じ場
は各節点の3方向の変位成分が自由度となっており、3み取れない情報だが、内ループのイタレーション回数
処理が順番に行われる。当然、競合が発生すると処理
ション回数は数十万回以上と十分に多いことが分かっあっても、Atomic コンストラクトを追加すると、追加所を同時に更新しようとすると）、その複数スレッドの自由度をまとめて計算しているからである。なお、こは高々27回と少ないのに対して、外ループのイタレー
速度は低下するが、実際には競合が発生しない場合でている。数十万以上あれば、GPU
にとっても十分な並しない場合と比べて処理速度が低下することに留意し処理が順番に行われる。当然、競合が発生すると処理のコードは、説明簡略化のため、元コードをかなり簡ション回数は数十万回以上と十分に多いことが分かっあっても、Atomic
コンストラクトを追加すると、追加列度である。つまり、イタレーション回数の少ない、て欲しい。
速度は低下するが、実際には競合が発生しない場合で素化したものであることに注意して欲しい。
にとっても十分な並しない場合と比べて処理速度が低下することに留意し
縮約計算の並列化オーバーヘッドが伴う内ループを無
なお、一つのコンストラクトを追加すると、追加Atomic コンストラクトでガードでき ている。数十万以上あれば、GPU
あっても、Atomic
列度である。つまり、イタレーション回数の少ない、
て欲しい。
この
SpMV
計算をどう並列化するのが良いかを考え
理に並列化する必要はない。従って、この事例での推るのは、一つの計算である。複数の計算のガードが必しない場合と比べて処理速度が低下することに留意し
縮約計算の並列化オーバーヘッドが伴う内ループを無
なお、一つの Atomic コンストラクトでガードできる。外ループのイタレーション間には依存性がないの
奨並列化方法は、内ループは並列化せず、外ループだ
要なときは、各計算のそれぞれに対して、Atomic コンて欲しい。
理に並列化する必要はない。従って、この事例での推るのは、一つの計算である。複数の計算のガードが必で、並列化が可能である。内ループ間のイタレーショ
け並列化することである。図 12 のディレクティブをストラクトを追加する必要がある。また、図 11 では
奨並列化方法は、内ループは並列化せず、外ループだ
要なときは、各計算のそれぞれに対して、Atomic
コン
なお、一つのAtomic コンストラクトでガードできるン間には依存性があるが、その内容は縮約演算（VAL1,
見ると、
外ループには Gang
Vector 並列を適用、最も用途の多いと思われる、配列要素を更新するとき
け並列化することである。図12 並列とのディレクティブをストラクトを追加する必要がある。また、図11 ではのは、一つの計算である。複数の計算のガードが必要
VAL2,
VAL3に対する
ReductionSum）なので、こちらも
内ループは逐次実行せよとコンパイラに指示してあるに使う Update クローズのみを示したが、Atomic コン 見ると、
外ループには Gang 並列と Vector 並列を適用、最も用途の多いと思われる、配列要素を更新するときなときは、各計算のそれぞれに対して、Atomic
コンス
並列化が可能である。更に、これは図12からは読み取のが分かるだろう。
ストラクトには、他にもRead/Write/Capture
クローズ
内ループは逐次実行せよとコンパイラに指示してあるに使う
Update
クローズのみを示したが、Atomic
コン
トラクトを追加する必要がある。また、図11では最も用れない情報だが、内ループのイタレーション回数はこのコードを OpenACC コンパイラでビルドすると、が存在する。
のが分かるだろう。
ストラクトには、他にもRead/Write/Capture クローズ途の多いと思われる、配列要素を更新するときに使う
高々27回と少ないのに対して、外ループのイタレー
正しい結果を計算できる
GPU コードが生成される。
このコードをOpenACC コンパイラでビルドすると、が存在する。
Update クローズのみを示したが、Atomic コンストラク
ション回数は数十万回以上と十分に多いことが分かっ
正しい結果を計算できる
GPU
コードが生成される。
2.7 有限要素法ソルバーの最適化
しかし、残念ながら、この状態では、
配列 A と配列 IDX
トには、他にもRead/Write/Capture クローズが存在する。 しかし、
2.7 ループの最適化の具体的な事例として、
有限要素法ソルバーの最適化
残念ながら、この状態では、配列 Aにとっても十分な並と配列 IDXている。数十万以上あれば、GPU
有限要素法のデータレイアウトが、選択した並列化方法とマッチ
ループの最適化の具体的な事例として、
有限要素法のデータレイアウトが、選択した並列化方法とマッチ
ソルバーを OpenACC で GPU 化したケースを説明する。 列度である。つまり、イタレーション回数の少ない、していないため、GPU の性能を十分には引き出せない。
2.7
有限要素法ソルバーの最適化
ソルバーをOpenACC で GPU 化したケースを説明する。 していないため、
GPU の性能を十分には引き出せない。
プログラムは、東京大学の中島研吾先生からお借りしGPU では、隣接したスレッドが、メモリ上に隣接配置
縮約計算の並列化オーバーヘッドが伴う内ループを無ループの最適化の具体的な事例として、有限要素法
プログラムは、東京大学の中島研吾先生からお借りしGPU では、隣接したスレッドが、メモリ上に隣接配置
理に並列化する必要はない。従って、この事例での推ソルバーを OpenACC で GPU 化したケースを説明する。
奨並列化方法は、内ループは並列化せず、外ループだプログラムは、東京大学の中島研吾先生からお借りしけ並列化することである。図12のディレクティブを見た GeoFEM-Cube-OMP/CG である。このプログラムは、ると、外ループには Gang 並列と Vector 並列を適用、内並列有限要素法プラットフォーム GeoFEM[2] に基づき、ループは逐次実行せよとコンパイラに指示してあるの一様な物性を有する Cube 形状を対象として三次元弾性が分かるだろう。
計算工学（24-6）

Vol.22, No.1 2017

チュートリアル

OpenACC で始める GPU コンピューティング : ループの最適化

このコードを OpenACC コンパイラでビルドすると、正しい結果を計算できる GPU コードが生成される。しかし、残念ながら、この状態では、配列 A と配列 IDXのデータレイアウトが、選択した並列化方法とマッチしていないため、GPU の性能を十分には引き出せない。GPU では、隣接したスレッドが、メモリ上に隣接配置されたデータをアクセスするときに、最もメモリアクセス効率が良くなるが [3]、図12のコードはその条されたデータをアクセスするときに、最もメモリアク件を満たしていない。
セス効率が良くなるが[3]、図 12 のコードはその条件
具体的に、配列データ A(1,j,i) への READ アクセスをを満たしていない。
考えよう。ここでは、外ループのみを並列化して、そ
具体的に、配列データ A(1,j,i)への READ アクセの各イタレーションを別々のスレッドに割り当てる方スを考えよう。
ここでは、外ループのみを並列化して、法を選択したので、例えば、イタレーション i=99を担その各イタレーションを別々のスレッドに割り当てる
当するスレッドが配列データ A(1,j,99) にアクセスする
方法を選択したので、例えば、イタレーション i=99とき、その次のイタレーション i=100を担当するスレッを担当するスレッドが配列データ A(1,j,99)にアクドは配列データ A(1,j,100) にアクセスすることになる。
セスするとき、その次のイタレーション i=100 を担当
Fortran の配列データの格納順序を考慮すると、この2つするスレッドは配列データ A(1,j,100)にアクセスすのデータ、A(1,j,99)と A(1,j,100)
はメモリ上で非常に離ることになる。Fortranの配列データの格納順序を考慮れたところに格納されている。隣接スレッドがメモリすると、この 2 つのデータ、A(1,j,99)と A(1,j,100)
上で非常に離れたところに配置されたデータにアクセ
はメモリ上で非常に離れたところに格納されている。
スすることになり、GPUのメモリアクセス効率は低下
隣接スレッドがメモリ上で非常に離れたところに配置する。
されたデータにアクセスすることになり、GPU のメモ

リアクセス効率は低下する。

この性能問題を解消するには、配列 A と配列 IDX のデータレイアウトを、例えば図13のように変更すれば良い。このように配列のデータレイアウトを変更すると、隣接スレッドがメモリ上で隣接配置されたデータにアクセスするようになり、GPU のメモリアクセス効率が向上する。実際に、このデータレイアウト変更の前後で、GPU 上での SpMV 計算性能は約3倍向上た。また、そのときのメモリバンド幅の使用効率は75% に達を、コンパイラにきめ細かく指示できるディレクティ
した（メモリバンド幅の使用効率は、プログラムのメモブが用意されている。もちろん、OpenACC には、CUDA
リアクセスパターンに依存するが、概ね80% 程度がそのようなアクセラレータ専用言語が提供するレベルのの上限）。多重ループの場合は特に、配列のデータレイ
自由度はない。しかし、有限要素法ソルバーの最適化
アウトが、選択した並列化方法とマッチしているかをの事例で示した様に、配列データレイアウト変更など注意して欲しい。
のコードリファクタリングを同時に行えば、OpenACC
で GPU 化したプログラムであっても、GPU 性能をほ3 おわりにぼフルに引き出すことも可能である。
今回は、我々が推奨する OpenACC によるアプリケー
本連載は今回で最後だが、連載の間にも、OpenACC
ション並列化サイクルの最後のステップ「ループの最適を用いたプログラムのGPU 対応事例は着実に増えて
化」
を
説
明
し
た。OpenACCには、ループ、 特 に 多 重いる。この連載が OpenACC を始めるきっかけとなれループで構成される処理をどのように並列化するかば幸いである。
を、コンパイラにきめ細かく指示できるディレクティ
ブ が 用 意 さ れ て い る。 も ち ろ ん、OpenACC に は、
■参考文献のようなアクセラレータ専用言語が提供するレ[1]CUDACUDA
C Programming Guide
ベルの自由度はない。しかし、有限要素法ソルバーのhttps://docs.nvidia.com/cuda/cuda-c-programming-gui
最適化の事例で示した様に、配列データレイアウト変
de/[2]更などのコードリファクタリングを同時に行えば、GeoFEM:
OpenACC
で GPU 化したプログラムであっても、GPU
http://geofem.tokyo.rist.or.jp/[3]性能をほぼフルに引き出すことも可能である。
How to Access Global Memory Efficiently in CUDA
本連載は今回で最後だが、連載の間にも、OpenACC
C/C++ Kernels
https://devblogs.nvidia.com/parallelforall/how-accessを用いたプログラムのGPU 対応事例は着実に増えていglobal-memory-efficiently-cuda-c-kernels/
る。この連載が
OpenACC を始めるきっかけとなれば幸
いである。
■参考文献
[1]
[2]
[3]

図 13 GeoFEM-Cube-OMP/CG
GeoFEM-Cube-OMP/CG の
計算コード
図13
のSpMV
SpMV
計算コード
（配列データレイアウト最適化後）（配列データレイアウト最適化後）

CUDA C Programming Guide
https://docs.nvidia.com/cuda/cuda-c-programming-guide/
GeoFEM:http://geofem.tokyo.rist.or.jp/
How to Access Global Memory Efﬁciently in CUDA C/C++
Kernels
https://devblogs.nvidia.com/parallelforall/how-access-globalmemory-efﬁciently-cuda-c-kernels/

この性能問題を解消するには、配列 A と配列 IDXのデータレイアウトを、例えば図 13 のように変更すれば良い。このように配列のデータレイアウトを変更
すると、隣接スレッドがメモリ上で隣接配置されたデータにアクセスするようになり、GPU のメモリアクセ
ス効率が向上する。実際に、このデータレイアウト変
更の前後で、GPU 上での SpMV 計算性能は約 3 倍向上た。
また、そのときのメモリバンド幅の使用効率は 75%に達した（メモリバンド幅の使用効率は、プログラムのメモリアクセスパターンに依存するが、概ね 80%程
度がその上限）。多重ループの場合は特に、配列のデー
タレイアウトが、選択した並列化方法とマッチしているかを注意して欲しい。
計算工学
3
おわりに

（24-7）

今回は、我々が推奨する OpenACC によるアプリケーション並列化サイクルの最後のステップ「ループの最適化」を説明した。OpenACC には、ループ、特に多重ループで構成される処理をどのように並列化するか

Vol.22, No.1 2017