SIMDアーキテクチャ

この1つの命令で複数の演算器を動かすといいうやり方は「SIMD(Single Instruction stream Multiple Data stream)」と呼ばれる。

図2.6 4演算を並列に実行するSIMD演算器

図2.6のようにレジスタと演算器のペアを4組並べ、1つの命令ユニットからの命令をすべての組に供給すれば、同じ命令で4つのデータを同時に処理することができる。これは、GPUだけでなく、Intel CPUのSSEやAVXでも使われている方式である。なお、ここでは演算器は1つの箱で描いているが、実際には、この中には、浮動小数点の積、和演算、整数演算、論理演算に加えて逆数や三角関数などを計算する演算器が含まれている。

4並列のSIMD方式で、頂点V0、V1、V2、…を処理する場合は、まず、V0~V3をそれぞれのレジスタと演算器ペアを使って処理し、次の頂点V4~V7を処理し、さらにV8~V11という風に処理していく。そして、GPUの場合は、V0~V3の処理が終わってからV4~V7の処理を始めるのではなく、それぞれを別のスレッドとして、V0~V3スレッドの最初の命令を実行したら、次のサイクルにはV4~V7スレッドの最初の命令、その次のサイクルにはV8~V11スレッドの最初の命令という風に、サイクルごとにスレッドを切り替えながら実行するのが普通である。このようにすれば、次に同じスレッドの命令を実行するのは何サイクルも後であるので、浮動小数点演算のようにレーテンシの長い命令であっても、レーテンシを隠すことができるというメリットがある。

SIMDアーキテクチャでの条件分岐命令への対応

すべてのデータの組に同じ命令を実行して行く頂点シェーダなどの場合は良いのであるが、複雑な処理になると条件分岐命令が必要になる場合がある。計算結果がある条件を満たす場合だけ次の命令列Aを実行し、条件を満たさない場合は命令列Aは実行しない、

if (条件) then{A}

というようなプログラムの場合である。

このようなケースに対応するため、SSEやAVXではマスク付きの演算命令をもっている。この命令はマスクされているレジスタ-演算器ペアでは、演算結果をレジスタに書き戻さず、結果として演算が行われなかったように動作する。この命令を使えば、条件が成立したレジスタ-演算器ペアではマスクなし、条件が成立しなかったレジスタ-演算器ペアではマスクをするようにすれば、

if (条件) then{A}

という命令列の処理ができる。

なお、通常の数値の比較ではCondition Codeがセットされるが、SIMDの比較命令では各レジスタ-演算器ペアのマスクを計算する命令を持っている。従って、このSIMDの比較命令でマスクレジスタをセットし、そのマスクを使ってSIMD演算を行えばよい。

通常のプロセサであれば、命令列Aを実行しない場合は、それだけ早く処理が終わるのであるが、マスクを使うSIMD計算では、命令列Aを実行しないレジスタ-演算器ペアにも、命令列Aの命令が供給されるので、命令の実行の有無にかかわらず、どちらも同じ実行時間がかかる。

SIMDのメモリアクセスの問題

SIMDのロード、ストアユニットは、図2.7に示すように、1つのアドレスから並列にデータをアクセスする。例えば、それぞれの演算は4バイトデータを扱う場合、最初のペアにはA番地からの4バイト、2番目のペアにはA+4番地からの4バイト、その次のペアにはA+8番地からの4バイト、最後のペアにはA+12番地からの4バイトという風に、データをロードする。ストアの場合はこの逆であるが、指定した番地から連続した16バイトに4つのレジスタのデータを格納する。

図2.7 SIMDのロード、ストア命令はメモリの連続番地をアクセス

このように連続番地のアクセスで良いという場合も多いのであるが、メモリから読んだ値に従って次のアクセスを行うという

for( ) C[i]= A[i]+B[indx[]i]]; 

のようなプログラムの場合は、アクセスするアドレスB[indx[i]]が連続であるとは限らない。

このようなケースを処理するためには、index[i]をアドレスレジスタに格納し、そのアドレスに従ってメモリをアクセスする図2.8のような機構が必要である。このようにばらばらのアドレスからデータを集めてくる機能をGather、逆にばらばらのアドレスにデータを格納する機能をScatterという。

図2.8 アドレス配列を使う間接アクセスの処理には、アドレスレジスタを使うアクセス機構が必要になる

IntelのSSEやAVX-1では、このような不連続なメモリアクセスはできず、マスク付きのロードストア命令(Intelではムーブ命令)を使って順に処理するか、SIMDでない通常のレジスタを使って1つずつロードして、SIMDレジスタに転送するなどの処理を行う必要があり、プログラムが面倒であった。

これに対して、AVX-2ではVGATHER命令がサポートされ、最大4つの倍精度浮動小数(8バイト)を不連続なメモリアドレスからSIMDレジスタに読み込む命令がサポートされた。しかし、この逆の4つの倍精度浮動小数をバラバラのアドレスに書き込むScatter命令はサポートされていないので、書き込み側は従来の方法を使わなければならない。

少し、話が横に逸れるが、スパコンの元祖ともいうべきCRAY-1では、最大64個の浮動小数点数からなるベクタ同士の演算ができ、レジスタに格納されたインデックスに従って、ばらばらなアドレスから最大64個のベクタデータを読み込むGather、ばらばらなアドレスにベクタデータを書き込むScatter命令があった。まあ、CRAY-1では1サイクルにベクタの1要素を処理するパイプライン処理を行っていたので、64要素のScatter/Gatherと言っても毎サイクル1要素ずつアドレスを変えて処理すれば良かったので、作り易かったという面もあるのであるが、SSEやAVX-1/2と比べて使いやすい命令であった。

このように不連続なメモリのアクセスに関しては、多少、使いにくい面があるのであるが、演算に関して言えば、並列にならべた演算器の数に比例して性能があがるので、ユニファイドシェーダのハードウェアとしてSIMDを使っているものもある。