SIMT実行の条件分岐

コンピュータのプログラムで、条件を判定して、その後の処理を変えるという機能を使わずに済むものはほとんど無いといってよい。CPUのマルチスレッド処理のように、実行するスレッドごとにプログラムとPC(プログラムカウンタ)があるプロセサの場合は、if文の条件が成立したスレッドではthen節の命令を実行し、条件が不成立のスレッドではelse節の命令を実行するようにすればよい。

しかし、NVIDIAのGPUの場合は32スレッドがワープという単位でまとめられており、32スレッドが同じ命令列を実行する。このため、1つのワープの中に、条件が成立するスレッドと不成立のスレッドが混在すると困ったことになる。

この問題を解決するため、GPUではスレッドごとに、それぞれの命令にその命令を実行するかしないかを指示するPredicateというビットを指定することができるようになっている。前の図3-32ではPredicateの値が、直接、命令の実行の可否を決めるように書いたが、実は図3-45のように、比較命令setpの結果のTrue/False(真/偽)をPredicateレジスタに格納し、各命令では\@pを先頭に着けることにより、Predicateレジスタを参照して命令実行の可否を決めるという方法が採られている。

なお、setp命令はすべてのスレッドで同じレジスタのペアを比較するが、レジスタの値はスレッドごとに異なっているので、比較結果もスレッドごとに異なる。そして、\@pの付いている命令はPredicateレジスタの値が真の場合は、その命令を実行し、偽の場合は、命令の実行をスキップする。

このため、図3-45のように、if文の条件をsetp命令で判定してPredicateレジスタに格納し、then句のInst1からInst5の命令は\@pを付けておくと、条件判定が真のスレッドではthen句の命令が実行され、偽のスレッドではthen句は実行されず、Inst6の命令が実行されるようになる。

ただし、スキップされた命令も実行された命令と同じ実行時間が掛かるので、スキップした分、実行が速くなるわけではない。この例ではthen句だけであるが、else句がある場合は、@!pを命令に前置すれば良い。「!」を付けるとPredicateレジスタの真偽を反転して実行の可否を決める。論理的には@pを付けたthen句の命令と\@!pを付けたelse句の命令をつなげればif~then~else~の処理ができるが、ifの条件が成立した場合も不成立の場合もthen句とelse句の両方を実行するだけの時間が掛かる。このため、性能の観点からは、then句、else句でスレッドごとに実行する命令が異なる部分はできるだけ短くすることが重要である。

1つのワープの中で条件判定が分かれる場合はPredicateを使わざるを得ないが、Warp全体で同じ方向に分岐する場合は分岐命令を使って、実行しない命令列は飛ばしてしまう方が効率が良い。この情報を伝えるため、PTXでは分岐命令の後に.uniという指定ができるようになっている。この指定を付けるとPTXは全スレッドが同じ方向に分岐するとみなしてマシン命令を生成する。

図3-45 Predicateレジスタを使うif条件分岐の実行

Predicateレジスタは1bitのレジスタである。PTXのドキュメントではPredicateレジスタは仮想レジスタであると書かれており、物理的に何個のPredicateレジスタがあるのかはPTXのドキュメントにもCUDA C Programming Guideにも書かれていない。

Predicateレジスタの内容を、直接読んだり、Predicateレジスタに値を直接書き込む命令は無いのであるが、Predicateの値に応じてレジスタに格納する値を選択するselp命令を使えば、内容を0/1に変換して汎用レジスタに書き込むことができる。また、このように変換すると、複数のPredicate値のAND、OR、XORなどを計算することができる。そして、setp命令で汎用レジスタの値をPredicate値に変換できる。従って、物理的なPredicateレジスタは1個しかなくても、汎用レジスタがあれば仮想的には複数のPredicateレジスタがあるように見せることが可能である。

Predicateレジスタが1個しかないと、分岐による枝分かれの中に、また、別の条件での枝分かれある場合には、Predicateレジスタ内容の入れ替えが必要になり効率が悪いので、何個かの物理Predicateレジスタを備えていると思われるが、その数は一定ではなく、GPUアーキテクチャの世代で変わっているのかも知れない。

余談であるが、32ビットのV7アーキテクチャのARMプロセサもこのようなPredicateを持っているが、新しい64ビットのV8アーキテクチャではPredicateは無くなってしまった。Predicateレジスタの指定に4ビットを必要としており、このため、ARM V7アーキテクチャでは汎用レジスタの指定には4ビットしか割り当てられず、SPARCやMIPS、POWERなどのRISCでは32個ある汎用レジスタが16個に制限されてしまう。レジスタが少ないのであふれたデータをメモリに追い出し、必要になれば再度読み出すという無駄な動作の頻度が高くなり、実行性能が下がってしまうという問題があった。このためV8ではPredicateを止めて32汎用レジスタに変更したと言われている。

なお、ARMやSPARC、POWERなどのRISCプロセサの命令長は32ビットであるが、GPUは大部分が64ビット長の命令を使っており、Predicateに4ビットを使ってもレジスタ指定には十分なビットがあるので、ARMとは事情が異なる。