GPU ShaderAnalyzerで遊んでみた

ComputeShaderを叩いていたらどこにボトルネックがあるのか気になったのでGPU ShaderAnalyzer v1.59.3208を入れて試しに解析させてみた。

対象ソース

以前記事にしたHSV変換コードをかけてみた。
ここで再掲する。

// hsv.hlsl
float3 rgb2hsv(float3 rgb)
{
	float3	hsv;
	float	rgb_max, rgb_min;
	float	diff;
	float	base;
	float	div1;
	float	div2;

	rgb_max = max(max(rgb.x, rgb.y), rgb.z);
	rgb_min = min(min(rgb.x, rgb.y), rgb.z);
	if(rgb_max == rgb.x){
		diff = rgb.y - rgb.z;	// G-B
		base = (rgb.y < rgb.z)? 360.0f : 0.0f;
	}
	if(rgb_max == rgb.y){
		diff = rgb.z - rgb.x;	// B-R
		base = 120.0f;
	}
	if(rgb_max == rgb.z){
		diff = rgb.x - rgb.y;	// R-G
		base = 240.0f;
	}

	div1 = (rgb_max == rgb_min)? 1.0f : (rgb_max - rgb_min);
	div2 = (rgb_max > 0.0f)? rgb_max : 1.0f;
	hsv.x = 60.0f * diff / div1 + base;
	hsv.y = (rgb_max - rgb_min) / div2;
	hsv.z = rgb_max;

	return hsv;
}
float3 hsv2rgb(float3 hsv)
{
	float3	rgb;
	int		Hi;
	float	f;
	float	p;
	float	q;
	float	t;

	Hi = fmod(floor(hsv.x / 60.0f), 6.0f);
	f = hsv.x / 60.0f - Hi;
	p = hsv.z * (1.0f - hsv.y);
	q = hsv.z * (1.0f - f * hsv.y);
	t = hsv.z * (1.0f - (1.0f - f) * hsv.y);

	if(Hi == 0){
		rgb.x = hsv.z;
		rgb.y = t;
		rgb.z = p;
	}
	if(Hi == 1){
		rgb.x = q;
		rgb.y = hsv.z;
		rgb.z = p;
	}
	if(Hi == 2){
		rgb.x = p;
		rgb.y = hsv.z;
		rgb.z = t;
	}
	if(Hi == 3){
		rgb.x = p;
		rgb.y = q;
		rgb.z = hsv.z;
	}
	if(Hi == 4){
		rgb.x = t;
		rgb.y = p;
		rgb.z = hsv.z;
	}
	if(Hi == 5){
		rgb.x = hsv.z;
		rgb.y = p;
		rgb.z = q;
	}

	return rgb;
}

あと呼び出し側。こっちは未掲載かな。
後日計測結果の記事で説明予定。

// test_hsv.hlsl
#include "hsv.hlsl"


#define		NUM_N		256

#define		NUM_THREAD_X	16
#ifdef THREAD_COVERED_ALL
	#define	NUM_THREAD_Y	(NUM_N / (NUM_THREAD_X * NUM_THREAD_Z))
#else
	#define	NUM_THREAD_Y	 4
#endif
#define		NUM_THREAD_Z	 1


RWTexture3D<float4> g_result : register( u0 );


[numthreads(NUM_THREAD_X, NUM_THREAD_Y, NUM_THREAD_Z)]
void CS(
	uint3 Gid : SV_GroupID,
	uint GI : SV_GroupIndex
)
{
#ifdef GI_IS_B
	uint3		rgb = uint3(Gid.x, Gid.y, Gid.z*NUM_THREAD_X*NUM_THREAD_Y*NUM_THREAD_Z+GI);
#else
	uint3		rgb = uint3(Gid.x*NUM_THREAD_X*NUM_THREAD_Y*NUM_THREAD_Z+GI, Gid.y, Gid.z);
#endif
	const float	div = 1.0f / 255.0f;

	g_result[rgb] = float4(hsv2rgb(rgb2hsv(div * rgb)), 1.0f);
}

アセンブリ出力

解析のついで?にアセンブリ言語コンパイルしたコードを表示してくれるみたい。

が選べる。
ただ、5870と6870で機械語の逆アセンブリが同じになっている。なんで??
6xxx系ってVLIW4じゃなかったっけ?

DirectXはこんな感じで。(先頭のコメントは長いので省略)

cs_5_0
dcl_globalFlags refactoringAllowed
dcl_uav_typed_texture3d (float,float,float,float) u0
dcl_input vThreadIDInGroupFlattened
dcl_input vThreadGroupID.xyz
dcl_temps 5
dcl_thread_group 16, 4, 1
mov r0.w, l(240.000000)
mov r1.w, l(120.000000)
imad r2.x, vThreadGroupID.x, l(64), vThreadIDInGroupFlattened.x
utof r3.z, r2.x
utof r3.xy, vThreadGroupID.yzyy
mul r4.xyz, r3.xyzx, l(0.003922, 0.003922, 0.003922, 0.000000)
mad r1.xyz, r3.xyzx, l(0.003922, 0.003922, 0.003922, 0.000000), -r4.yzxy
lt r3.x, r4.x, r4.y
and r0.y, r3.x, l(0x43b40000)
mov r0.xz, r1.xxzx
max r1.x, r4.x, r4.z
max r3.z, r4.y, r1.x
eq r1.xz, r3.zzzz, r4.xxyx
movc r0.xy, r1.xxxx, r1.ywyy, r0.xyxx
movc r0.xy, r1.zzzz, r0.zwzz, r0.xyxx
mul r0.x, r0.x, l(60.000000)
min r0.z, r4.x, r4.z
min r0.z, r4.y, r0.z
eq r0.w, r3.z, r0.z
add r0.z, -r0.z, r3.z
movc r0.w, r0.w, l(1.000000), r0.z
div r0.x, r0.x, r0.w
add r0.x, r0.y, r0.x
mul r0.y, r0.x, l(0.016667)
round_ni r0.y, r0.y
mul r0.y, r0.y, l(0.166667)
ge r0.w, r0.y, -r0.y
frc r0.y, |r0.y|
movc r0.y, r0.w, r0.y, -r0.y
mul r0.y, r0.y, l(6.000000)
round_z r0.w, r0.y
ftoi r0.y, r0.y
mad r0.x, r0.x, l(0.016667), -r0.w
add r0.w, -r0.x, l(1.000000)
lt r1.x, l(0.000000), r3.z
movc r1.x, r1.x, r3.z, l(1.000000)
div r0.z, r0.z, r1.x
mad r0.w, -r0.w, r0.z, l(1.000000)
mad r0.x, -r0.x, r0.z, l(1.000000)
add r0.z, -r0.z, l(1.000000)
mul r3.xyw, r0.zxzw, r3.zzzz
ieq r1.xyzw, r0.yyyy, l(1, 2, 3, 4)
ieq r0.x, r0.y, l(5)
movc r4.xy, r1.xxxx, r3.yzyy, r3.zwzz
mov r4.z, r3.x
movc r0.yzw, r1.yyyy, r3.xxzw, r4.xxyz
movc r0.yzw, r1.zzzz, r3.xxyz, r0.yyzw
movc r0.yzw, r1.wwww, r3.wwxz, r0.yyzw
movc r0.xyz, r0.xxxx, r3.zxyz, r0.yzwy
mov r2.yzw, vThreadGroupID.yyzz
mov r0.w, l(1.000000)
store_uav_typed u0.xyzw, r2.xyzw, r0.xyzw
ret 
// Approximately 53 instruction slots used

ILはDirectXとほぼ同じ(直値にラベルが振られているくらいしか差が見えなかった)なので省略。
これが5870では

; --------  Disassembly --------------------
00 ALU: ADDR(32) CNT(96) 
      0  x: LSHL        ____,  R0.z,  6      
         y: MOV         T1.y,  (0x42F00000, 120.0f).y      
         z: LSHL        T0.z,  R1.x,  6      
         w: LSHL        ____,  R0.y,  4      
         t: U_TO_F      T0.y,  R1.y      
      1  x: MUL_e       T0.x,  PS0,  (0x3B808081, 0.003921568859f).x      
         y: MOV         T3.y,  (0x43700000, 240.0f).y      
         z: ADD_INT     ____,  PV0.x,  PV0.w      
         w: MOV         R2.w,  (0x3F800000, 1.0f).z      
         t: U_TO_F      T0.w,  R1.z      
      2  x: MUL_e       T1.x,  PS1,  (0x3B808081, 0.003921568859f).x      
         y: ADD_INT     ____,  R0.x,  PV1.z      
         z: MOV         R0.z,  R1.z      
         t: MOV         R0.y,  R1.y      
      3  x: ADD_INT     R0.x,  PV2.y,  T0.z      
         y: MULADD_e    T0.y,  T0.y,  (0x3B808081, 0.003921568859f).x, -PV2.x      
         w: SETGT_DX10  ____,  PV2.x,  T0.x      
      4  x: AND_INT     T2.x,  PV3.w,  0x43B40000      
         t: U_TO_F      ____,  PV3.x      
      5  y: MULADD_e    T2.y,  PS4,  (0x3B808081, 0.003921568859f).x, -T0.x      
         w: MUL_e       ____,  PS4,  (0x3B808081, 0.003921568859f).x      
      6  x: MIN_DX10    ____,  T0.x,  PV5.w      
         z: MULADD_e    T0.z,  T0.w,  (0x3B808081, 0.003921568859f).x, -PV5.w      
         w: MAX_DX10    ____,  T0.x,  PV5.w      
      7  x: MAX_DX10    T3.x,  T1.x,  PV6.w      
         z: MIN_DX10    ____,  T1.x,  PV6.x      
      8  x: SETE_DX10   T0.x,  T1.x,  PV7.x      
         y: SETE_DX10   ____,  PV7.x,  PV7.z      
         z: SETE_DX10   ____,  T0.x,  PV7.x      VEC_120 
         w: ADD         T0.w,  PV7.x, -PV7.z      
         t: SETGT_DX10  ____,  PV7.x,  0.0f      
      9  x: CNDE_INT    ____,  PV8.y,  PV8.w,  1065353216      
         y: CNDE_INT    ____,  PV8.z,  T2.x,  T1.y      
         z: CNDE_INT    T0.z,  PS8,  1065353216,  T3.x      
         w: CNDE_INT    ____,  PV8.z,  T0.y,  T0.z      
     10  y: CNDE_INT    T2.y,  T0.x,  PV9.y,  T3.y      
         z: CNDE_INT    ____,  T0.x,  PV9.w,  T2.y      VEC_021 
         t: RCP_e       T1.z,  PV9.x      
     11  w: MUL_e       ____,  PV10.z,  (0x42700000, 60.0f).x      
         t: RCP_e       ____,  T0.z      
     12  y: MULADD_e    T2.y,  PV11.w,  T1.z,  T2.y      
         z: MUL_e       T1.z,  T0.w,  PS11      
     13  x: ADD         ____, -PV12.z,  1.0f      
         w: MUL_e       ____,  PV12.y,  (0x3C888889, 0.01666666754f).x      
     14  x: FLOOR       ____,  PV13.w      
         z: MUL_e       T2.z,  T3.x,  PV13.x      
     15  z: MUL_e       ____,  PV14.x,  (0x3E2AAAAB, 0.1666666716f).x      
     16  y: SETGE_DX10  ____,  PV15.z, -PV15.z      
         w: FRACT       ____,  |PV15.z|      
     17  x: CNDE_INT    ____,  PV16.y, -PV16.w,  PV16.w      
     18  z: MUL_e       ____,  PV17.x,  (0x40C00000, 6.0f).x      
     19  y: TRUNC       ____,  PV18.z      
         w: NOP         ____      
         t: F_TO_I      T0.w,  PV18.z      
     20  x: MULADD_e    ____,  T2.y,  (0x3C888889, 0.01666666754f).x, -PV19.y      
         y: SETE_INT    T2.y,  PS19,  1      
         z: SETE_INT    T0.z,  PS19,  2      
         w: SETE_INT    T1.w,  PS19,  3      
         t: SETE_INT    T2.w,  PS19,  4      
     21  x: SETE_INT    T2.x,  T0.w,  5      
         z: ADD         ____, -PV20.x,  1.0f      
         w: MULADD_e    ____, -PV20.x,  T1.z,  1.0f      
     22  x: MUL_e       T0.x,  T3.x,  PV21.w      
         y: MULADD_e    ____, -PV21.z,  T1.z,  1.0f      
     23  z: CNDE_INT    ____,  T2.y,  T3.x,  PV22.x      
         w: MUL_e       T0.w,  T3.x,  PV22.y      
     24  x: CNDE_INT    ____,  T0.z,  PV23.z,  T2.z      
         y: CNDE_INT    ____,  T2.y,  PV23.w,  T3.x      
         z: CNDE_INT    ____,  T0.z,  T2.z,  PV23.w      
     25  x: CNDE_INT    ____,  T1.w,  PV24.z,  T3.x      
         z: CNDE_INT    ____,  T0.z,  PV24.y,  T3.x      
         w: CNDE_INT    ____,  T1.w,  PV24.x,  T2.z      
     26  x: CNDE_INT    ____,  T1.w,  PV25.z,  T0.x      
         y: CNDE_INT    ____,  T2.w,  PV25.w,  T0.w      VEC_102 
         z: CNDE_INT    ____,  T2.w,  PV25.x,  T3.x      VEC_120 
     27  x: CNDE_INT    R2.x,  T2.x,  PV26.y,  T3.x      
         z: CNDE_INT    R2.z,  T2.x,  PV26.z,  T0.x      VEC_021 
         w: CNDE_INT    ____,  T2.w,  PV26.x,  T2.z      
     28  y: CNDE_INT    R2.y,  T2.x,  PV27.w,  T2.z      
01 MEM_RAT_STORE_TYPED: RAT(0)[R0], R2,  VPM 
END_OF_PROGRAM

てな感じ。VLIW5ってコンパイル時点で複数命令をまとめるんだね。
言われてみればゴトウさんの記事でもそんな表記を何度か見かけた気がする。*1
GPUが命令発行するときにくっつけるのかと勘違いしてた。

見た感じ、(x, y, z, w, t)の5つ命令スロットがあって「t」のスロットが5番目のSpecial Function Unit(名前あってるかな)っぽいね。
tが使われる割合はそんなに高くないけど、今回みたいにfloat4の演算が少ない場合は複数行が1命令にまとめられるからVLIWに利点があるね。(とはいえ、1〜2命令の行もあるから常に良いとは限らないか)

これがGCNだとどう変換されるのか気になるなぁ。自分の理解している内容だとfloat×16個に対して同一命令1個を発射するイメージだから今回みたいに処理対象がfloat1個分だけだと短くならない気がするけど。ん?まてよ。逆に16スレッド同時に実行できたりするのかな。VLIWだと1SIMD Engine==16スレッド固定(予想)だけど、GCNだと1CUに最大64スレッド割り当てできたりする??
あ゛ー、気になる。けど、7xxx系ハードは手元にないー。わざわざそのためだけに購入するのはアレだし…
ってちょうどさっきのゴトウさんの記事に書いてあるね。

従来のAMD GPUの場合は、1個のSIMD Engineにつき各サイクル、VLIWユニット側では1個のWavefront、16個のスレッドしか走っていない(テクスチャやロード/ストアは別)。それに対して、新アーキテクチャGCNでは、1個のCUの中で各サイクル4 Wavefrontで64スレッドが並列に走る。

なるほど。予想通りだね。勢いで7870買わなくてよかった(^^;
そうするとGCNだとVLIW5よりもHSV変換みたいにfloat1個ずつ処理するタイプの処理に強いってことだね。
最大値は1命令×64スレッド vs 5命令×16スレッドでVLIW5が強いけど、常に5命令発射はできないから典型的にはGCNの方が強いね。最大4倍速になりえるし。
GCNはその代わりにレイテンシを隠せるだけの大量のスレッドを用意する必要があるって感じなのかな。

解析出力

で、解析結果はこんな感じで出てきた。

Name ALU GPR Avg BottleNeck(Bi) Throughput(Bi) %s\Clock(Bi)
5770 29 3 2.90 ALU Ops 4690 M Threads\Sec 5.52
5870 29 3 1.45 ALU Ops 9379 M Threads\Sec 11.03
6870 29 3 2.07 ALU Ops 6952 M Threads\Sec 7.72

なぜか7870は選択できず。ShaderAnalyzerが非対応なのか単に実行した環境が6xxx系なので非表示なのかCatalystのバージョンが古いのかなんなのかは不明。
少なくともCatalystはすごい古いと思う。リビジョン番号はよくわからないけど、「Catalyst Control Center バージョン」は「2012.0415.1414.23551」ってなってる。
2012って…去年?

しかし、これどうやって見れば良いの??さっぱりわからん。

GRPってどれのこと?Rレジスタかな。他にもTレジスタとかPVレジスタとかPSレジスタとかいろいろ使ってるけど…Rレジスタなら確かにR0〜R2の3つ使っているように見える。
Avgってのも良くわからない。ALU:TEX(Bi)ってのと同じだから演算命令とテクスチャフェッチ命令の比率かな?
Throughputもおかしくない?5870は850MHz×20SIMD Engile×16StreamCore÷29命令==9379.3なのであってる気もするけど、6870が極端に低いような気が…VLIW4だからって下がりすぎだと思うけど。
でもよくよく考えたらHSVのコードは256^3=16Mスレッドあるから単純計算で1secに600回近く実行できることになるけど本当かなぁ…