2016年1月6日水曜日

[gcc, spu] spu版gcc最適化問題

以下は2004年に解析したもの

$lrの無駄なセーブ


リーフ関数にも関わらず、$lrをセーブしている部分がある。

imm001:
il $2,1 lqa $13,v001.0 il $12,127 stqd $15,-16($sp) il $15,0 hbr .L2,$lr stqd $16,-32($sp) nop $127
il $10,-128 stqd $lr,16($sp) ← 後でロードもしないので無駄
il $9,-1
$lrがいかなる関数にも関わらずスタック上にセーブされる

変数のstore処理の無駄

例題

void f(void)
{
static char c;
c = 1;
}
コンパイル結果

nop $127
hbr .L2, $lr
lnop
lnop
il $2, 1 (1)
lqa $6, c.0 (2)
cbd $3, 0($sp) (3)
nop $127
nop $127
shufb $5, $2, $6, $3 (4)
stqa $5, c.0 (5)
.L2:
bl $lr
(1)~(5)が即値のメモリストア
(1) 即値1をレジスタに設定($2 = 00000001 00000001 00000001 00000001)
(2) $6にcの内容を設定($6 = xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx)
(3) シャッフルバイト用の情報作成($3 = 03111213 14151617 18191a1b 1c1d1e1f)
    この情報でシャッフルバイトによる型変換を行ないます。
(4) シャッフルバイト処理。($5 = 01xxxxxx xxxxxxxx xxxxxxxx xxxxxxxx)
(5) $5の内容をストア

メモリのロード・ストアにはqword単位のものしかありません。
そのため、メモリ上で後続の情報を壊さないためにも、一回メモリから qword分読み込んで、その後必要な情報を修正して再度書き直さなければなりません。
但し、qwordでアラインしている場合、この保障は必要なくなります。

nop $127
hbr .L2, $lr
lnop
lnop
ilhu $2, 0x0100 (1)
stqa $2, c.0 (2)
.L2:
bl $lr
上記のようなメモリを保護する処理をしている限り変数を全て 16byte アラインにする必要はないのではないか?

変数のstore処理の無駄(2)


配列、構造体のように連続したメモリ上にある場合、メモリアクセスを一括に処理することで、コードを少なくすることが出来る。

変更前
il $3, 1
lqa $8, c
il $2,2
hbr .L2, $lr
cbd $9, 0($sp)
nop
cbd $7, 1($sp)
shufb $5, $3, $8, $9
stqa $5,c
lqa $6,c+1
shufb $4,$2,$6,$7
stqa $4,c+1
nop
.L2:
bi $lr
変更後

il $3, 1
lqa $8, c
il $2,2
hbr .L2, $lr
cbd $9, 0($sp)
nop
cbd $7, 1($sp)
shufb $5, $3, $8, $9
shufb $4, $2, $5, $7
stqa $4,c
nop
.L2:
bi $lr

レジスタ割り当て


リターンレジスタに絡んだレジスタ割り当てが甘い。

ソース

unsigned short f(unsigned short a, unsigned short b)
{
return a * b;
}
アセンブラ

nop
hbr .L2, $lr
mpy $2, $3, $4
ila $5, 65535
and $4, $2, $5 → and $3, $2, $5
ori $3, $4, 0
nop
nop
nop
nop
nop
.L2:
bi $lr

無駄なnop


乗算をする場合、nopが入ります。
このnopは速度をあげるために必要なようです。(理由は不明)

以下の関数を考えます。定数 11 を欠けることで mpyh 命令等を使うようになります。

[ソース]
int foo(int a)
{
return a * 11;
}

[アセンブラ]
foo:
nop $127 (*)
hbr .L2, $lr
il $4, 11
mpyui $6, $3, 11
mpyh $5, $3, $4
nop $127 (*)
nop $127
nop $127
nop $127
a $3, $5, $6
nop $127 (*)
.L2:
bi $lr
(*) マークの nop を削除すると呼び出し部分も含めた処理が 301 サイクルだったのが 299 サイクルになります。また、この関数のサイズも 48byte から 40byte に削減できます。
これ以上削ると、逆にサイクル数が増えます。

0 件のコメント:

コメントを投稿