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回近く実行できることになるけど本当かなぁ…