GPUの命令の実行方法

CPUでは、基本的に1つの命令列で一組のデータを処理する。これに対して、GPUでは命令列は1つであるが、多数のデータの組を並列に処理するという方法を採る。例えば、3次元図形の頂点の座標変換を行うには、それぞれの頂点座標に変換行列を掛ける。この時、複雑な図形であれば頂点は多数あるが、変換行列を掛ける手順(プログラムの命令列)はすべて同じである。つまり、1つの命令列で、多数の異なる頂点座標データに同じ変換行列を掛けてやれば、多数の頂点の座標の変換を行うことができる。これは、座標変換を行うプログラム(スレッド)を並列に実行したことと同じである。NVIDIAは、この処理方法をSIMT(Single Instruction Multiple Thread)と呼んでいる。

GK110 GPUのデータパスの構造(筆者作成)

GK110のような複雑なチップの全貌を1つの図にまとめるのは難しいが、おおよそ、上の図のような構造になっている。NVIDIAのGPUでは、上の図のReg FileとCUDAコアのペアをレーン(Lane)と呼び、1つのレーンが1つのスレッドを実行する。そして、32レーンのグループに同じ命令列が供給され、32のスレッドを並列に実行する。このように32のスレッドが1つの実行単位となり、NVIDIAはこれをワープ(Warp)と呼んでいる。

前述のようにKepler GPUのSMには192のCUDAコアがあるので、ワープを処理する32 CUDAコアの組が6組存在することになる。また、DP Unitは64個であるので、倍精度浮動小数点のワープを処理するハードウェアは2組、LD/STとSFUのワープの処理ハードウェアは1組ということになる。なお、LD/STやSFUは、この図ではCore etc.と書かれた部分に含まれることになるが、DP Unitは2組、LD/STやSFUは1組のハードウェアしかないので、実際は、そのハードウェアを使用する命令が出てきたワープにダイナミックに割り当てられることになる。

1つの命令で複数のデータを複数の演算器で並列に処理するという点では、この構造はx86のSSEやAVXと同じであるが、SSEやAVXの場合は、並列に処理されるデータは一つのスレッドの中のデータであり、GPUのように異なるスレッドを並列に実行しているわけではない。

SSEやAVXの処理はSIMD(Single Instruction Multiple Data)と呼ばれ、1つの命令で複数のデータのまとまり(ベクトル)を処理するので一種のベクトル演算である。しかし、SIMTの場合はそれぞれのスレッドは1つのデータを扱うスカラ処理であり、GPUの処理をベクトル演算と呼ぶのは、筆者は抵抗がある。

Kepler GPUでは32レーンが同じ命令を実行するが、

if ( i > 10 )
 { then側の文 }
 else 
{ else側の文}

のような条件分岐がある構文の場合は、あるスレッドではifの条件が成立しthen側の文を実行し、別のスレッドでは条件は不成立でelse側の文を実行するという場合がでてくる。そうなると32レーンすべてで同じ命令を実行することはできなくなってしまう。

Kepler GPUでの条件分岐のあるワープの実行状況(1)。赤がthen側の命令、緑がelse側の命令を示す

この図では赤の矢がthen側の命令、緑の矢がelse側の命令を表しており、左側の0~31のスレッドでは、一部のスレッドではthen側の命令を実行し、残りのスレッドではelse側の命令を実行するという状況を示している。

GPUは、1つのワープを実行する32レーンの命令はまったく同じでなければならないので、条件成立と不成立のスレッドがワープ内に混在する場合は、まず、then側の命令をすべてのレーンに供給する。条件が成立したレーンでは普通にその命令を実行するが、条件不成立のレーンでは、演算器には同じ命令が供給されるが、その命令は入力データの読み出しや結果の書き出しを行わないことで、実質的に命令を実行しないようにする。

次にelse側の命令をすべてのレーンに供給し、条件不成立のレーンでは命令を実行し、条件が成立したレーンでは、実質的に命令を実行しない。このような処理を行うことで、32レーンに同じ命令を供給するという原則を守りながら、正しい結果を保証している。

しかし、この図に見られるように、ハードウェアとしては常にthen側の命令とelse側の命令を両方とも実行するだけの処理時間が掛かってしまう。プログラムから条件分岐を無くすことは難しいが、GPUの場合はthen側の文とelse側の文、特に実行される頻度の小さい方の文はできるだけ短くして、両方を実行することによるオーバヘッドを小さくすることが望ましい。

なお、図の右側のスレッド32~63のワープでは、すべてのスレッドが条件不成立でelse側の命令を実行し、then側の命令の実行は必要ないという状態を示している。このようにワープ内で条件の成立(不成立)が揃っている場合は、then(else)側の命令を実行する時間は不要であり、両方の命令を実行することによる性能の低下はない。従って、可能であれば、ワープ間では条件の成立、不成立の違いがあるが、ワープ内では分岐方向が揃うようにプログラムを書き変えると条件分岐による性能ロスを無くすことができる。