SIMD化

手作業によるSIMD化はなかなか時間がかかっています。個人的な目標はCPIを1.0以下にすることですが、思った以上にCPIが減らないので取り合えず現状のパッチをアップします。SIMD化した部分は#ifdef __SIMD__で囲っています。

パッチはこちら

個人的にはMMXやSSEなどを直接使ったことはないので、SIMDプログラミングの経験値は低いです。その点はご容赦願います。

SIMD化の例

前回のプロファイリングで上位に挙がっていたix_max関数が割と簡単なので例にあげてみます。ix_max関数のCのコードは以下の通りです。配列の最大値を求めています。

int 
ix_max(const int *ix, const int *end)
{
    int max1 = 0, max2 = 0;

    do {
	int x1 = *ix++;
	int x2 = *ix++;
	if (max1 < x1) 
	    max1 = x1;

	if (max2 < x2) 
	    max2 = x2;
    } while (ix < end);
    if (max1 < max2) 
	max1 = max2;
    return max1;
}

これのアセンブラソースをspu-gcc_timingというツールにかけると静的解析を見ることができます。これで各命令がストールなく動作するか、並列実行できるかなどを見ることができます。静的解析結果は以下の通り。

                                                               	.type	ix_max, @function
                                                               ix_max:
0D 01                                                          	ori	$7,$3,0
1D 012345                                                      	hbra	.L48,.L40
0D  12                                                         	ori	$10,$4,0
1D  1234                                                       	fsmbi	$9,0
0D   23                                                        	il	$8,0
1D   2                                                         	lnop
                                                               .L40:
0D    34                                                       	ai	$12,$7,4
1D    345678                                                   	lqd	$13,0($7)
1      -567890                                                 	lqd	$11,0($12)
1        ---9012                                               	rotqby	$2,$13,$7
0d           01                                                	ai	$7,$7,8
1d           -1234                                             	rotqby	$3,$11,$12
0              23                                              	clgt	$6,$10,$7
0               34                                             	cgt	$4,$2,$9
0                -56                                           	cgt	$5,$3,$8
0                  67                                          	selb	$9,$9,$2,$4
0D                  78                                         	selb	$8,$8,$3,$5
                                                               .L48:
1D                  7890                                       	brnz	$6,.L40
0                    -90                                       	cgt	$7,$8,$9
0                      -12                                     	selb	$3,$9,$8,$7
0D                       2                                     	nop	$127
1D                       2345                                  	bi	$lr

最初の0とか1とかがどのパイプラインで実行しているか、次の数字や-が実行状態です。-はストール状態です。右側はアセンブラコード。メインループは.L40から.L48の間です。あまり並列動作できず、1ループで6回ストールしています。

これを見て初めて気がついたのですが(よく読むとチュートリアルにちゃんと書いてありますが)、SPUは実はクワッドワード単位のロードとストアしかできないのです。そのため、通常のコード(上記例だとintの配列操作)だとクワッドワード単位でロードしてシフトすることで必要なワードデータのロードが完了します。ストアはもっと最悪で書き込む値をシフトして、書き込み先の値をロードして合成してから書きこみます。以前、なんでSHUFのストールが多いかわからなかったのですが、SPUの作りを知って理解しました。こりゃーたぶん通常のコードのままだとPPUに負けます。

これを以下のコードに直してみました。

int 
ix_max(const int *ix, const int *end)
{
  vec_int4 *v_ix;
  vec_int4 max1 = (vec_int4)(0),max2,max3,max4;
  vec_int4 data1,data2,data3,data4;
  vec_uint4 result1,result2,result3,result4;
  int i,j,k;

  max4 = max3 = max2 = max1;
  if((int)ix & 0x0000000F){
    max1 = spu_splats(*ix++);
    max2 = spu_splats(*ix++);
  }

  v_ix = (vec_int4*)ix;
  i = (int)end - (int)ix;
  k = i & 0x0000000F;
  i >>= 4;
  j = i & 3;
  i >>= 2;

  for(;i > 0;i--){
    data1 = *v_ix++;
    result1 = spu_cmpgt(data1,max1);
    max1 = spu_sel(max1,data1,result1);

    data2 = *v_ix++;
    result2 = spu_cmpgt(data2,max2);
    max2 = spu_sel(max2,data2,result2);

    data3 = *v_ix++;
    result3 = spu_cmpgt(data3,max3);
    max3 = spu_sel(max3,data3,result3);

    data4 = *v_ix++;
    result4 = spu_cmpgt(data4,max4);
    max4 = spu_sel(max4,data4,result4);
  }
  for(;j > 0;j--){
    data1 = *v_ix++;
    result1 = spu_cmpgt(data1,max1);
    max1 = spu_sel(max1,data1,result1);
  }

  if(k){
    ix = (const int *)v_ix;
    data1 = spu_splats(*ix++);
    data1 = spu_insert(*ix++,data1,0);
    result1 = spu_cmpgt(data1,max1);
    max1 = spu_sel(max1,data1,result1);
  }

  result2 = spu_cmpgt(max2,max1);
  max2 = spu_sel(max1,max2,result2);

  result4 = spu_cmpgt(max4,max3);
  max4 = spu_sel(max3,max4,result4);

  result1 = spu_cmpgt(max2,max4);
  max1 = spu_sel(max4,max2,result1);

  max2 = spu_rlqwbyte(max1,4);
  max3 = spu_rlqwbyte(max1,8);
  max4 = spu_rlqwbyte(max1,12);

  result2 = spu_cmpgt(max2,max1);
  max2 = spu_sel(max1,max2,result2);

  result4 = spu_cmpgt(max4,max3);
  max4 = spu_sel(max3,max4,result4);

  result1 = spu_cmpgt(max2,max4);
  max1 = spu_sel(max4,max2,result1);

  return si_to_int((qword)max1);
}

結構でかくなりました。このコードの静的解析結果は以下の通りです。

                                                               	.type	ix_max, @function
                                                               ix_max:
0D 01                                                          	il	$14,0
1D 0123                                                        	shlqbyi	$8,$3,0
0D  12                                                         	andi	$2,$3,15
1D  1234                                                       	shlqbyi	$6,$4,0
0D   23                                                        	ori	$17,$14,0
1D   2345                                                      	shlqbyi	$15,$14,0
0D    34                                                       	ori	$16,$14,0
1D    3456                                                     	brz	$2,.L47
0D     45                                                      	ai	$10,$3,4
1D     456789                                                  	lqd	$11,0($3)
0D      56                                                     	ai	$8,$3,8
1D      5                                                      	lnop
0D       67                                                    	ila	$5,66051
1D       678901                                                	lqd	$9,0($10)
0d        78                                                   	ila	$4,66051
1d        ---0123                                              	rotqby	$7,$11,$3
1             -2345                                            	rotqby	$3,$9,$10
1               -4567                                          	shufb	$14,$7,$7,$5
0d                5                                            	nop	$127
1d                -6789                                        	shufb	$15,$3,$3,$4
                                                               .L47:
0D                  78                                         	sf	$13,$8,$6
1D                  789012                                     	hbra	.L67,.L61
0                    89                                        	ori	$11,$8,0
0                     9012                                     	rotmai	$12,$13,-6
0                      0123                                    	rotmai	$8,$13,-4
0                       12                                     	andi	$18,$13,15
0                        -34                                   	cgti	$6,$12,0
0                          45                                  	andi	$13,$8,3
0D                          5                                  	nop	$127
1D                          5678                               	brz	$6,.L58
                                                               .L61:
0D                           67                                	ai	$12,$12,-1
1D                           678901                            	lqd	$27,0($11)
0D                            7                                	nop	$127
1D                            789012                           	lqd	$25,16($11)
0D                             89                              	cgti	$19,$12,0
1D                             890123                          	lqd	$23,32($11)
1                               901234                         	lqd	$21,48($11)
0                                01                            	ai	$11,$11,64
0                                 -23                          	cgt	$26,$27,$14
0                                   34                         	cgt	$24,$25,$15
0                                    45                        	cgt	$22,$23,$17
0                                     56                       	cgt	$20,$21,$16
0                                      67                      	selb	$14,$14,$27,$26
0                                       78                     	selb	$15,$15,$25,$24
0D                                       89                    	selb	$17,$17,$23,$22
1D                                       8                     	lnop
0D                                        90                   	selb	$16,$16,$21,$20
                                                               .L67:
1D                                        9012                 	brnz	$19,.L61
                                                               .L58:
0D                                         01                  	cgti	$12,$13,0
1D                                         012345              	hbra	.L66,.L62
0d                                          1                  	nop	$127
1d                                          -2345              	brz	$12,.L60
                                                               .L62:
0D                                            34               	ai	$13,$13,-1
1D                                            345678           	lqd	$30,0($11)
0                                              45              	ai	$11,$11,16
0                                               56             	cgti	$28,$13,0
0D 0                                             ---9          	cgt	$29,$30,$14
1D                                                  9          	lnop
0D -12                                                         	selb	$14,$14,$30,$29
                                                               .L66:
1D  1234                                                       	brnz	$28,.L62
                                                               .L60:
0D   2                                                         	nop	$127
1D   2345                                                      	brz	$18,.L56
0D    34                                                       	ai	$36,$11,4
1D    345678                                                   	lqd	$37,0($11)
0D     45                                                      	ila	$38,66051
1D     4567                                                    	cwd	$34,0($sp)
1       567890                                                 	lqd	$35,0($36)
1        -----1234                                             	rotqby	$32,$35,$36
1              2345                                            	shufb	$33,$37,$37,$38
1               ---6789                                        	shufb	$31,$32,$33,$34
0                   ---01                                      	cgt	$18,$31,$14
0                       -23                                    	selb	$14,$14,$31,$18
                                                               .L56:
0D                        -45                                  	cgt	$48,$15,$14
1D                         456789                              	hbr	.L65,$lr
0                           56                                 	cgt	$47,$16,$17
0                            67                                	selb	$46,$14,$15,$48
0                             78                               	selb	$44,$17,$16,$47
0                              -90                             	cgt	$45,$46,$44
0d                               -12                           	selb	$41,$44,$46,$45
1d                                --3456                       	rotqbyi	$43,$41,4
1                                    4567                      	rotqbyi	$17,$41,8
1                                     5678                     	rotqbyi	$40,$41,12
0                                      -78                     	cgt	$42,$43,$41
0                                        -90                   	cgt	$39,$40,$17
0                                          01                  	selb	$16,$41,$43,$42
0                                           12                 	selb	$14,$17,$40,$39
0                                            -34               	cgt	$15,$16,$14
0                                              -56             	selb	$3,$14,$16,$15
0D                                               6             	nop	$127
                                                               .L65:
1D                                               6789          	bi	$lr

コードが増えていますが、メインループは.L61から.L67の間です。相変わらずあまり並列動作はできていませんが、ストールは一つしかしていません。オリジナルコードと比べて1ループで8倍の処理を行うので実行コード自体も減ってストールも減っています。

今回はこのようなSIMD化をいくつかの関数で行ってみました。詳しくはパッチを見てください。

結果

今回の結果は以下の通りです。パフォーマンス解析(動的解析)の方がたぶん現実に近いと思うのでこちらの結果のみです。恒例の3秒WAVファイルのエンコード結果です。

systemsim % mysim spu 5 display statistics
SPU DD3.0
***
Total Cycle count               1164904915
Total Instruction count         643
Total CPI                       1811671.59
***
Performance Cycle count         1164904915
Performance Instruction count   685337277 (629133623)
Performance CPI                 1.70 (1.85)

Branch instructions             36201307
Branch taken                    26165073
Branch not taken                10036234

Hint instructions               10594989
Hint hit                        17663931

Contention at LS between Load/Store and Prefetch 17830093

Single cycle                                         389671183 ( 33.5%)
Dual cycle                                           119731220 ( 10.3%)
Nop cycle                                             19247350 (  1.7%)
Stall due to branch miss                             189334053 ( 16.3%)
Stall due to prefetch miss                               25488 (  0.0%)
Stall due to dependency                              376267721 ( 32.3%)
Stall due to fp resource conflict                          910 (  0.0%)
Stall due to waiting for hint target                  26889219 (  2.3%)
Stall due to dp pipeline                                     6 (  0.0%)
Channel stall cycle                                   43737756 (  3.8%)
SPU Initialization cycle                                     9 (  0.0%)
-----------------------------------------------------------------------
Total cycle                                         1164904915 (100.0%)

Stall cycles due to dependency on each pipelines
 FX2        28476210 (  7.6% of all dependency stalls)
 SHUF       122265468 ( 32.5% of all dependency stalls)
 FX3        10339489 (  2.7% of all dependency stalls)
 LS         117697723 ( 31.3% of all dependency stalls)
 BR         256232 (  0.1% of all dependency stalls)
 SPR        379 (  0.0% of all dependency stalls)
 LNOP       0 (  0.0% of all dependency stalls)
 NOP        0 (  0.0% of all dependency stalls)
 FXB        0 (  0.0% of all dependency stalls)
 FP6        78549863 ( 20.9% of all dependency stalls)
 FP7        18682351 (  5.0% of all dependency stalls)
 FPD        6 (  0.0% of all dependency stalls)

The number of used registers are 128, the used ratio is 100.00
dumped pipeline stats
systemsim %
1164904915/3000000000=0.39 [sec]

約7.7倍速です。5月19日のSIMD前のパフォーマンス解析のサイクル数が1545099715なので、そのときの75%のサイクル数となり、そこそこ速くなっています。

0.5秒のWAVで実行した結果も貼ります。Athlonで速くなったとは言っても3秒のWAVのパフォーマンス解析はやっぱり遅いです。約1時間コース。0.5秒ならその1/6の時間ですむので。

systemsim % mysim spu 6 display statistics
SPU DD3.0
***
Total Cycle count               186784983
Total Instruction count         643
Total CPI                       290489.85
***
Performance Cycle count         186784983
Performance Instruction count   107788871 (98916031)
Performance CPI                 1.73 (1.89)

Branch instructions             5791247
Branch taken                    4229544
Branch not taken                1561703

Hint instructions               1697612
Hint hit                        2805465

Contention at LS between Load/Store and Prefetch 2928000

Single cycle                                          60407925 ( 32.3%)
Dual cycle                                            19254053 ( 10.3%)
Nop cycle                                              2998968 (  1.6%)
Stall due to branch miss                              31490654 ( 16.9%)
Stall due to prefetch miss                                4968 (  0.0%)
Stall due to dependency                               59804649 ( 32.0%)
Stall due to fp resource conflict                          150 (  0.0%)
Stall due to waiting for hint target                   4098273 (  2.2%)
Stall due to dp pipeline                                     6 (  0.0%)
Channel stall cycle                                    8725328 (  4.7%)
SPU Initialization cycle                                     9 (  0.0%)
-----------------------------------------------------------------------
Total cycle                                          186784983 (100.0%)

Stall cycles due to dependency on each pipelines
 FX2        4238984 (  7.1% of all dependency stalls)
 SHUF       18879227 ( 31.6% of all dependency stalls)
 FX3        1615677 (  2.7% of all dependency stalls)
 LS         18772810 ( 31.4% of all dependency stalls)
 BR         37078 (  0.1% of all dependency stalls)
 SPR        94 (  0.0% of all dependency stalls)
 LNOP       0 (  0.0% of all dependency stalls)
 NOP        0 (  0.0% of all dependency stalls)
 FXB        0 (  0.0% of all dependency stalls)
 FP6        13518721 ( 22.6% of all dependency stalls)
 FP7        2742052 (  4.6% of all dependency stalls)
 FPD        6 (  0.0% of all dependency stalls)

The number of used registers are 128, the used ratio is 100.00
dumped pipeline stats
systemsim %

考察

速度はまだまだですが、それ以上にCPIがあまり改善していないのがうれしくないです。どうもストール数も減ったけど実行コードも減ったのでCPIがあまり変わっていないようです。パフォーマンス解析を見ると依存関係によるストールは大幅に減っていますが、ブランチミスがむしろ増えています。ブランチミスの対策はブランチをやめるか__builtin_expect関数を使うことのようですが、うまくブランチミスを減らせてないです。今後はさらなるSIMD化とともにブランチミスを減らす方法を模索する必要がありそうです。