Shader内のRegister使用数と実行効率に関して

Shader内のRegister使用数と実行効率に関して考えてみます

Shaderが使用するRegister数

まず、今回は話をNVIDIAのFermiとKeplerに絞ります。これら2つのGPUのアーキテクチャでは、Shaderを内部で実行する際には、スカラー演算ユニットを使用します。したがって、DirectXのアセンブラで見受けられる4要素のベクトル演算命令は、実際にはスカラー命令に分解されて実行されます。あまり馴染みが無いとはおもいますが、CUDAのPTXのISAが、GPU内で実際に実行されるコードに近いものだと思われます。したがって、Shader内で使用されるレジスタ数を考えるときは、1要素ごとにRegisterを消費していくものだと考える必要があります。つまり、float3の1要素とfloatの3要素はどちらも3つのレジスタを消費するものと考えます。

Shaderが使用するRegister数が多くなるとどうなるか

Shaderが使用するRegisterが増加するとGPU内で何が起こるかを考えてみます。まず、KeplerではRegisterはGPU内のSMXと呼ばれる演算ユニットのRegster file内に確保されます。NVIDIAが公表しているスライドなどを見るとRegister fileの項には65536x32Bitと書いてあり、一見すると潤沢にレジスタがあり、何の問題も無いように思えます。しかし、実際にはこのRegister fileを多数のThreadで共有します。そのため、使用するRegisterの数が多くなると、同時にスケジューリングされるThreadの数が低下します。極端に考えれば、Registerを65536個使用するShaderはSMX内で1本しか実行といったイメージです。(あくまでイメージです)

ではSMX内には192個のCUDACoreがあるので、
65536/192 = 341
使用レジスタ数が341個以下ならば、SMX内のCUDACoreがすべて動作できるはずなので良好なパフォーマンスが得られるのかというと、そうでもありません。実行ThreadのスケジューリングはWarpと呼ばれる32Threadを実行単位としてスケジューリングされます。GK104のSMXのスケジューラーは最大64Warpを同時にスケジューリングすることができます。したがって、
32 x 64 = 2048
ひとつのSMXで最大で2048のThreadを同時にスケジューリングすることができます。したがって、SMX内に最大限にThreadを充填するためには、
65536 / 2048 = 32
Threadあたり32以下のRegister使用数に抑えることが、スケジューリングの観点からは、もっとも望ましい形と言えると思います。

しかし、先ほど説明した通り、CUDACoreは192個しかありません。なぜこのような構造になっているのでしょうか。一見すると多すぎるThreadのスケジューリングですが、このようにすることで、Textureの読み込みなどで発生するLatency(Textureの読み込み命令が発行されて、実際にTextureの値がRegisterに格納されるまでの間)に他のThreadの命令を実行することで、実行中のcontext全体でこれらのLatencyを隠蔽することが出来るからです。
Shaderが使用するRegister数が増加すると、同時にスケジューリングされるWarpの数が減少します。同時にスケジューリングされるWarpの数が減少すると、上記のようなLatencyが伴う処理を行ったときに、発生するLatencyを隠蔽出来なくなります。これらはマクロな視点から見れば、使用Register数が少ない場合に比べて、テクスチャや定数などの読み込みが遅いと感じることにつながると思われます。

Register pressureをどの程度考慮するべきか

では、どんなShaderも使用Register数を32以下にするべきでしょうか。答えはNoです。そもそも、この値はKepler固有のもので他のGPUアーキテクチャには当てはまりません。また、使用レジスタ数の削減のために非効率なコードを記述しても、これが必ず良い結果をもたらす保証はありません。隠蔽される可能性のある遅延がそこに存在しなければ、ただの非効率なコードとなってしまうからです。これに関しては実際に試すしか方法はありません。また、Keplerに限定すると使用レジスタ数を32以下からさらに削減することは、並列実効性の観点ではまったく意味がありません。
ただし一般論として、多数のRegisterを使用すると並列実効性が低下して、予期せぬ性能低下を招く可能性があるのは多くのGPUで当てはまると思われます。また、PixelShaderやComputeShaderなど、特に多数のThreadで並列実行される可能性の高いShaderについてはRegister使用数を気をつけた方が良いと思います。

もうすこし詳しく

CUDA tool kitの中に、Register使用数と同時にスケジューリングされるWarpの数の関係を計算するためのツールがあります。

CUDA GPU Occupancy Calculator
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v5.0\tools\CUDA_Occupancy_Calculator.xls

MS Excelが無いと実行できないのですが、実行すると下記のような表を見ることが出来ます。
Image3

まずはじめに、左側1番の緑のところで、ComputeCapabilityを選択します。2.1を選択すればFermi世代のGPU、3.0を選択すればGK104以下のKepler世代のGPUとなります。3.5はGK110になります。次にオレンジ色の2番目の項にThreadあたりの使用レジスタ数を入力するとSMX(SM)あたりで同時にスケジューリングされるWarpの数がわかります。右側の2番目のグラフが使用Register数とWarpの関係になります。
上図はGK104以下のGPUに相当するものです。Register使用数が32までは64Warpが同時にスケジューリングされますが、使用Register数が33の場合は48Warpとなります。ひとつだけRegister使用数が多いだけですが、同時にスケジューリングされるThread数は75%に低下します。41~48になるとWarpの数は40に減少し、同時にスケジューリングされるThread数は63%に低下します。ここで勘違いしないように、念を押しますが、これらはGPUの演算性能が75%や63%に低下するという話ではありません。先ほど説明したLatencyの隠蔽の機会が減少すると考えてください。処理の内容によっては、まったく性能に影響の無い場合もあります。

ついでなので、Fermi世代のRegister pressureの傾向も載せておきます。こちらは1SMあたり最大48Warpをスケジューリングすることが出来ますが、その際に使用できるRegister数は20です。21~24では40Warp(83%)となります。25~32では32Warp(67%)となります。

Image4

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中