第4ステップはカーネルの実行を最適化する
並列実行を指定するloopディレクティブであるが、OpenACCでは、さらに詳細な指定ができるようになっている。
図24に示すように、loop directiveにはprivateとreductionという指定がつけられる。
Privateはループを実行するスレッドごとに指定された変数のコピーを作るという指定である。ループ変数のi、jはprivateな変数である必要があるが、ループ変数はデフォルトでprivateになるので指定の必要はない。しかし、同じ変数名でもスレッドごとにコピーが必要な変数はprivateとして記述する必要がある。
Reductionは指定された変数の、全スレッドの結果を1つにまとめるという指定である。まとめ方としては、Max、Minや各種の論理演算を指定することができるようになっている。Privateとreductionは最適化ではなく、意図した動作をさせるために必須の指定である。
OpenACCでは、図25に示すように、ギャング、ワーカ、ベクタの3レベルの並列実行をサポートしている。そして、ループの実行に対して、ギャングやワーカの数やベクタの長さを指定することができるようになっている。また、並列実行できないループはseqをつけることで、順次実行を指示することができる。
OpenACCでは、同時に実行され、同期して実行されるスレッドのグループはベクタとして指定する。NVIDIAのGPUでは、ベクタの長さはワープサイズの32の倍数とすることが望ましく、128、256、512などが適当であるという。
ベクタのグループがワーカで、複数のベクタをまとめたものであるが、通常は考えなくて良いという。
そして、最上位が複数のワーカをまとめたギャングである。1つのギャングに属するすべてのスレッドは同じSM(Streaming Multiprocessor)で実行され、ローカルメモリ経由でデータを受け渡すことができる。これを図示したのが図26である。うまく指定すれば性能を上げることができるが、GPUハードウェアの作りと直結しており、筆者にはOpenACCが目指すハイレベルの記述とはそぐわない感じがする。
そしてloopディレクティブでは性能を上げるためのcollapseとtileという指定が使える。
collapseは複数のループを1つの長いループにまとめてしまう機能で、回数の少ないループがある場合に、性能を向上させるために使うことができる。次の図27の例ではN回のループの内側に4回のループがあるケースを、collapse(2)を指定することにより、2つのループを1つの4*N回のループにまとめるという変形を行っている。ループが長くなると、一般的には性能を向上させることができる。
また、loopディレクティブにはtile指定をつけることができる。図28の例では、i、jともに0~n-1まで順に変化するコードとなっているが、tile(8,8)を指定すると、iを0~7、jを0~7と変化させ、8×8のブロック単位で処理し、その次のブロックを処理するというコードを生成する。行列の乗算などでは、このようにレジスタ、あるいはローカルメモリに入る範囲のデータだけで大量の演算を行うことにより、時間のかかるGPUメモリへのアクセスの回数を減らして性能を上げることができる。