NVIDIAが7月30日に開催した「GTC Japan 2013」において、東京工業大学(東工大)松岡研究室の星野氏が、「OpenACCの理想と現実」と題する発表を行った。その前のコンパイラ各社の発表は、半分程度の入りであったが、この発表では会場が満員になる盛況で関心の高さがうかがえた。

OpenACCの理想と現実と題する発表を行う星野氏

GPUのプログラミング用にNVIDIAはCUDAを提供しているが、これは、プログラムをCPUで実行する部分とGPUで実行する部分に分け、処理するデータをCPUのメモリからGPUのメモリに転送し、結果は逆にGPUのメモリからCPUのメモリに転送するなどの処理を明示的に書く必要があり、なかなか敷居が高い。

これに対して、ソースプログラムの中に、ここからここまではGPUで実行する部分と指定すると、自動的にプログラムの分割やデータの転送をやってくれるという仕様がOpenACCである。

現在、CとFORTRAN対応のOpenACCコンパイラが出ており、これらの言語のソースプログラムにOpenACCの並列化を指示するコメント行を追加するだけで済む。そのため、プログラミングは簡単であり、生産性も高い。また、CUDAはNVIDIAのGPU専用であるが、OpenACCは業界標準であり、可搬性が高いというのも大きなメリットである。というのが「OpenACCの理想」である。

OpenACCの理想。簡単で生産性が高く、可搬性が良い(出典:星野氏のGTC Japan 2013での発表スライド。以下の使用画像もすべて同様)

しかし、本当にOpenACCは簡単なのか?、十分な性能が得られるのか?、可搬性は良いのかを実際に検証する必要があるので、星野氏は、行列積と7点ステンシルという簡単な2種のベンチマークプログラムとJAXAとの共同研究でUPACSという実プログラムのOpenACCによる並列化とCUDAへの書き直しを行ったという。

ただし、UPACSは10万行にもおよぶFORTANプログラムであり、この研究では実行時間の約90%を占める3つの計算フェーズだけをGPU化の対象としている。

OpenACCに対応したコンパイラを提供しているのは、現在、PGI、CAPS、そしてスパコンメーカーのCRAYの3社である。東工大では、これらの3社のOpenACC対応コンパイラが使えるので、これらを使って並列化した結果をもとに、「OpenACCの現実」部分の発表を行った。

まず、OpenACCは簡単かという点であるが、CUDAで書く場合は、行数が多くなるが、ほぼ決まりきった内容なので、CUDAで最低限動くプログラムを書くのは大して難しくは無いという。しかし、性能が出るプログラムを書くのは難しいので、ポイントはOpenACCでCUDAに近い性能が出るかどうかということになる。

OpenACCは簡単か?

また、共通メモリのマルチプロセッサ用のOpenMPと比べると、CPUとGPUのメモリが分離しているので、データ転送が必要であり、その通路の中でPCI Expressの部分のバンド幅が小さく転送に時間がかかるので、転送を最適化する必要がある点が煩わしい。また、構造体が使えないという点も、後に述べるように、大量の書き換えが必要となり、大変、煩わしいという。

次の図は行列積と7点ステンシルのコードに、追加したOpenACCの指示行をカラーで表示している。これを見た感じでは、それほど難しくは無いという印象である。

行列積と7点ステンシルプログラムのOpenACCによる並列化指示。KernelsでGPUにオフロードする部分を指示し、dataでデータ転送、loopでマッピングを指示

しかし、実プログラムのUPACSでは、構造体の中にデータやデータの大きさを示す値などがまとめられて入っている。しかし、OpenACCでは構造体の参照を含む文をGPUにオフロードできない仕様になっているので、構造体から要素を取り出したコードに書き換える必要がある。Structure of Arrays形式であれば各アレイをポインタで取り出せば済むが、Arrays of Structures形式となっている場合は、いったん、コピーを作る必要がある。

そして、データ転送を毎回行うと大変遅いので、dataディレクティブを追加してデータ転送を最適化するという作業が必要になるという。

これらの作業を行ったソースコードを3社のOpenACCコンパイラでGPU並列化したものと、手作業でCUDAで書いたコードの性能を比較している。ただし、この結果は、あくまでも、現在の3社のコンパイラの結果であり、コンパイラのレベルが変わると結果も変わってくる。

OpenACC化は、行列積では9行の書き換え、7点ステンシルでは7行の書き換えであるが、CUDA化は、それぞれ26行と35行の変更となり、OpenACCのほうがかなり簡単といえる。

行列積(左)と7点ステンシルの性能比較

単純に並列化した青と、スレッドの割付を最適化した赤の2つのグラフがあるが、青のほうで比べると、CUDAのハンドコードに比べて70%かそれを若干下回る性能となった。一方、赤のグラフで比べると、PGIコンパイラは行列積では95%、ステンシルでは97%とCUDAに迫る性能を達成しているが、他の2社のコンパイラは55%~74%とあまり成績がよくない。

ただし、これはOpenACCのスレッド割付の仕様があいまいで、他の2社はPGIの指定は仕様に違反しており、ズルした結果で性能が高くなっていると主張しているとのことである。

次の図ではJAXAのUPACSをOpenACCとCUDAで書き換えた場合の性能を、各種の最適化を行った場合について示している。そして、上の表が書き換え行数を示しており、ベースラインの場合、OpenACCでは1788行の書き換え、CUDAでは5607行の書き換えになっている。また、OpenACCで一番最適化したコードでは3296行の書き換え、CUDAで一番最適化されたコードでは8870行が書き換えられている。

UPACSのOpenACC(左)とCUDAでのGPU並列化の性能。6本の棒グラフは最適化のレベルの違いを示している

この図で見ると、ベースラインでは、相対性能で、OpenACCは1.4程度であるのに対して、CUDAは2.2程度、5番目の最適化ではOpenACCは1.8程度、CUDAは2.8程度となっており、まあ、OpenACCはCUDAの70%程度の性能となっている。

CUDAではメモリを経由しなければ、並列に実行するスレッドの間でデータを受け渡すことができないが、使用するメモリはDRAMのグラフィックスメモリでなくても、チップ内の小容量のシェアードメモリ経由でデータを受け渡すことができる。しかし、OpenACCではスレッド間のデータ受け渡しを記述することができないので、シェアードメモリを使うコードを作ることができない。このため、一番、最適化レベルが高い版で比べると、OpenACC版は、CUDA版の42%の性能に留まっている。

ということで、OpenACCはCUDAに比べて書き換え行が少ないが、UPACSのように構造体を多用している場合は、書き換え行がかなり多くなり、必ずしも簡単にGPU並列化ができると言えない場合もある。また、性能はCUDAで書き直した場合の60~70%程度で、ちょっと物足りないレベルであるという。

また、OpenACC1.0では仕様にあいまいな点があり、各社の実装がまちまちで、可搬性に問題が出る場合があるという。しかし、OpenACC2.0では、これらのあいまいさが改良されており、可搬性も改善されると期待される。さらに、各社のコンパイラも時間とともにバグが減り、性能も上がっていくことが期待される。というのが現実であるという。