第4ステップはカーネルの実行を最適化する

並列実行を指定するloopディレクティブであるが、OpenACCでは、さらに詳細な指定ができるようになっている。

図23 loopディレクティブはさらに詳細な指定ができる

図24に示すように、loop directiveにはprivateとreductionという指定がつけられる。

図24 Privateと指定すると並列に実行されるスレッドごとに指定された変数のコピーが作られる。Reductionを指定すると、全スレッドの結果のリダクションが行われる

Privateはループを実行するスレッドごとに指定された変数のコピーを作るという指定である。ループ変数のi、jはprivateな変数である必要があるが、ループ変数はデフォルトでprivateになるので指定の必要はない。しかし、同じ変数名でもスレッドごとにコピーが必要な変数はprivateとして記述する必要がある。

Reductionは指定された変数の、全スレッドの結果を1つにまとめるという指定である。まとめ方としては、Max、Minや各種の論理演算を指定することができるようになっている。Privateとreductionは最適化ではなく、意図した動作をさせるために必須の指定である。

OpenACCでは、図25に示すように、ギャング、ワーカ、ベクタの3レベルの並列実行をサポートしている。そして、ループの実行に対して、ギャングやワーカの数やベクタの長さを指定することができるようになっている。また、並列実行できないループはseqをつけることで、順次実行を指示することができる。

図25 ループの並列実行のやり方の指定

OpenACCでは、同時に実行され、同期して実行されるスレッドのグループはベクタとして指定する。NVIDIAのGPUでは、ベクタの長さはワープサイズの32の倍数とすることが望ましく、128、256、512などが適当であるという。

ベクタのグループがワーカで、複数のベクタをまとめたものであるが、通常は考えなくて良いという。

そして、最上位が複数のワーカをまとめたギャングである。1つのギャングに属するすべてのスレッドは同じSM(Streaming Multiprocessor)で実行され、ローカルメモリ経由でデータを受け渡すことができる。これを図示したのが図26である。うまく指定すれば性能を上げることができるが、GPUハードウェアの作りと直結しており、筆者にはOpenACCが目指すハイレベルの記述とはそぐわない感じがする。

図26 ギャング、ワーカ、ベクタの3レベルの並列指定

そしてloopディレクティブでは性能を上げるためのcollapseとtileという指定が使える。

collapseは複数のループを1つの長いループにまとめてしまう機能で、回数の少ないループがある場合に、性能を向上させるために使うことができる。次の図27の例ではN回のループの内側に4回のループがあるケースを、collapse(2)を指定することにより、2つのループを1つの4*N回のループにまとめるという変形を行っている。ループが長くなると、一般的には性能を向上させることができる。

図27 collapse(2)を指定して、2重ループを1重ループに変形

また、loopディレクティブにはtile指定をつけることができる。図28の例では、i、jともに0~n-1まで順に変化するコードとなっているが、tile(8,8)を指定すると、iを0~7、jを0~7と変化させ、8×8のブロック単位で処理し、その次のブロックを処理するというコードを生成する。行列の乗算などでは、このようにレジスタ、あるいはローカルメモリに入る範囲のデータだけで大量の演算を行うことにより、時間のかかるGPUメモリへのアクセスの回数を減らして性能を上げることができる。

図28 tile指定は指定されたサイズでのブロッキングを行う