最適化オプションはまだあります.次は,より高度な最適化を行いたいという場合に使うものです.
上記のオプションは前号で説明したものの検証ですが,次は検証と説明を同時に行います.
● -fstrength-reduce
ループの強度削減と繰り返し変数の削除の最適化を実行します.ループの外に出せる計算は外に出して,ステップ数を減らします.
元のCソースをリスト13に,最適化オプションなしのアセンブラソースをリスト14に,最適化オプション付きのアセンブラソースをリスト15に示します.
〔リスト13〕test04.c
|
#include <stdio.h>
int main()
{
int iTbl[3];
int max=3;
int i;
int j = 5;
int k = 6;
for(i = 0; i < max; i++)
{
iTbl[i] = j * k;
}
return 0;
}
|
|
〔リスト14〕test04.s
|
.file "test04.c"
.version "01.01"
gcc2_compiled.:
.text
.align 4
.globl main
.type main,@function
main:
pushl %ebp
movl %esp,%ebp
subl $40,%esp
movl $3,-16(%ebp)
movl $5,-24(%ebp)
movl $6,-28(%ebp)
movl $0,-20(%ebp)
.p2align 4,,7
.L3:
movl -20(%ebp),%eax
cmpl -16(%ebp),%eax
jl .L6
jmp .L4
.p2align 4,,7
.L6:
movl -20(%ebp),%eax
movl %eax,%edx
leal 0(,%edx,4),%eax
leal -12(%ebp),%edx
movl -24(%ebp),%ecx
imull -28(%ebp),%ecx
movl %ecx,(%eax,%edx)
.L5:
incl -20(%ebp)
jmp .L3
.p2align 4,,7
.L4:
xorl %eax,%eax
jmp .L2
.p2align 4,,7
.L2:
movl %ebp,%esp
popl %ebp
ret
.Lfe1:
.size main,.Lfe1-main
.ident "GCC: (GNU) 2.95.3 20010315 (release)"
|
|
〔リスト15〕test04.s(-fstrength-reduce)
|
.file "test04.c"
.version "01.01"
gcc2_compiled.:
.text
.align 4
.globl main
.type main,@function
main:
pushl %ebp
movl %esp,%ebp
subl $24,%esp
movl $30,%edx
movl $2,%ecx
leal -4(%ebp),%eax
.p2align 4,,7
.L21:
movl %edx,(%eax)
addl $-4,%eax
decl %ecx
jns .L21
xorl %eax,%eax
movl %ebp,%esp
popl %ebp
ret
.Lfe1:
.size main,.Lfe1-main
.ident "GCC: (GNU) 2.95.3 20010315 (release)"
|
|
この例では変数jとkの積が不変になるはずなので,最適化後は即値30(5と6の積)をレジスタにセットしています.最適化前はそのままループの中で乗算しています.
● -fthread-jumps
条件分岐の後に別の条件分岐があり,その比較が最初の比較に包括されるものかどうかを検査する最適化を実行します.もし包括されるものなら,最初の条件分岐先は,二番目の条件分岐先かその直後のどちらかに,二番目の比較条件の真偽が既知かどうかにしたがって変更されます.
元のCソースをリスト16に,最適化オプションなしのアセンブラソースをリスト17に,最適化オプション付きのアセンブラソースをリスト18に示します.
〔リスト16〕test05.c
|
#include <stdio.h>
int main()
{
int res = 0;
int i = 0;
if (i == 0)
{
if (i <= 0 )
{
res = 1;
}
if (i == 0 )
{
res = 1;
}
if (i >= 0 )
{
res = 0;
}
}
return res;
}
|
|
〔リスト17〕test05.s
|
.file "test05.c"
.version "01.01"
gcc2_compiled.:
.text
.align 4
.globl main
.type main,@function
main:
pushl %ebp
movl %esp,%ebp
subl $24,%esp
movl $0,-4(%ebp)
movl $0,-8(%ebp)
cmpl $0,-8(%ebp)
jne .L3
cmpl $0,-8(%ebp)
jg .L4
movl $1,-4(%ebp)
.L4:
cmpl $0,-8(%ebp)
jne .L5
movl $1,-4(%ebp)
.L5:
cmpl $0,-8(%ebp)
jl .L3
movl $0,-4(%ebp)
.L6:
.L3:
movl -4(%ebp),%edx
movl %edx,%eax
jmp .L2
.p2align 4,,7
.L2:
movl %ebp,%esp
popl %ebp
ret
.Lfe1:
.size main,.Lfe1-main
.ident "GCC: (GNU) 2.95.3 20010315 (release)"
|
|
〔リスト18〕test05.s(-fthread-jumps)
|
.file "test05.c"
.version "01.01"
gcc2_compiled.:
.text
.align 4
.globl main
.type main,@function
main:
pushl %ebp
movl %esp,%ebp
xorl %eax,%eax
movl %ebp,%esp
popl %ebp
ret
.Lfe1:
.size main,.Lfe1-main
.ident "GCC: (GNU) 2.95.3 20010315 (release)"
|
|
この例では変数iは必ずゼロです.ネストした条件の真中にしか分岐しません.最適化後は単純明快に1をレジスタにセットして戻しています.最適化前はそのまま比較を繰り返しています.
● -fcse-follow-jumps
共通部分式削除(CSE)の際に,条件分岐命令のうち,その条件分岐先にほかの経路を通って到達することがないものを探し出します.
元のCソースをリスト19に,最適化オプションなしのアセンブラソースをリスト20に,最適化オプション付きのアセンブラソースをリスト21に示します.
〔リスト19〕test06.c
|
#include <stdio.h>
int main()
{
int res = 0;
int i = 0;
if (i == 0)
{
if (i == 0 )
{
res = 1;
}
else
{
res = 0;
}
}
else
{
res = 0;
}
return res;
}
|
|
〔リスト20〕test06.s
|
.file "test06.c"
.version "01.01"
gcc2_compiled.:
.text
.align 4
.globl main
.type main,@function
main:
pushl %ebp
movl %esp,%ebp
subl $24,%esp
movl $0,-4(%ebp)
movl $0,-8(%ebp)
cmpl $0,-8(%ebp)
jne .L3
cmpl $0,-8(%ebp)
jne .L4
movl $1,-4(%ebp)
jmp .L5
.p2align 4,,7
.L4:
movl $0,-4(%ebp)
.L5:
jmp .L6
.p2align 4,,7
.L3:
movl $0,-4(%ebp)
.L6:
movl -4(%ebp),%edx
movl %edx,%eax
jmp .L2
.p2align 4,,7
.L2:
movl %ebp,%esp
popl %ebp
ret
.Lfe1:
.size main,.Lfe1-main
.ident "GCC: (GNU) 2.95.3 20010315 (release)"
|
|
〔リスト21〕test06.s(-fcse-follow-jumps)
|
.file "test06.c"
.version "01.01"
gcc2_compiled.:
.text
.align 4
.globl main
.type main,@function
main:
pushl %ebp
movl %esp,%ebp
movl $1,%eax
movl %ebp,%esp
popl %ebp
ret
.Lfe1:
.size main,.Lfe1-main
.ident "GCC: (GNU) 2.95.3 20010315 (release)"
|
|
この例では変数iは必ずゼロです.最適化後は単純明快に1をレジスタにセットして戻しています.最適化前はそのまま冗長な処理,条件分岐を繰り返しています.
● -fcse-skip-blocks
上記の項目に似ています.絶対に到達しない条件分岐ブロックを無視し,コードを作成しません.
元のCソースをリスト22に,最適化オプションなしのアセンブラソースをリスト23に,最適化オプション付きのアセンブラソースをリスト24に示します.
〔リスト22〕test07.c
|
#include <stdio.h>
int main()
{
int res = 0;
int i = 0;
if (i != 0)
{
res = 1;
}
return res;
}
|
|
〔リスト23〕test07.s
|
.file "test07.c"
.version "01.01"
gcc2_compiled.:
.text
.align 4
.globl main
.type main,@function
main:
pushl %ebp
movl %esp,%ebp
subl $24,%esp
movl $0,-4(%ebp)
movl $0,-8(%ebp)
cmpl $0,-8(%ebp)
je .L3
movl $1,-4(%ebp)
.L3:
movl -4(%ebp),%edx
movl %edx,%eax
jmp .L2
.p2align 4,,7
.L2:
movl %ebp,%esp
popl %ebp
ret
.Lfe1:
.size main,.Lfe1-main
.ident "GCC: (GNU) 2.95.3 20010315 (release)"
|
|
〔リスト24〕test07.s(-fcse-skip-blocks)
|
.file "test07.c"
.version "01.01"
gcc2_compiled.:
.text
.align 4
.globl main
.type main,@function
main:
pushl %ebp
movl %esp,%ebp
xorl %eax,%eax
movl %ebp,%esp
popl %ebp
ret
.Lfe1:
.size main,.Lfe1-main
.ident "GCC: (GNU) 2.95.3 20010315 (release)"
|
|
この例では変数iは必ずゼロなので最適化後は変数iがゼロでない条件分岐を削除し,レジスタに0をセットして戻しています.最適化前はそのまま冗長な処理,条件分岐を繰り返しています.
● -frerun-cse-after-loop
ループ最適化の実行後に共通部分式の削除をもう一度実行します.-fstrength-reduceで行ったことを徹底して行います.
● -frerun-loop-opt
ループ最適化を徹底して行います.-fstrength-reduceで行ったことです.ループ内で変化しない式やアドレス計算がチェックされます.そして,そうした計算はループの外に移され,その評価値がレジスタに格納されます.
● -fgcse
グローバル共通部分式を最後に削除するパスをコンパイラが実行します.元のCソースをリスト25に,最適化オプションなしのアセンブラソースをリスト26に,最適化オプション付きのアセンブラソースをリスト27に示します
〔リスト25〕test08.c
|
#include <stdio.h>
void test();
int gData=8;
int i = 1;
int main()
{
int resTbl[100];
test();
gData = 8+i;
for (i=0;i<100;i++)
{
resTbl[i] = i*20;
}
gData = 8+i;
test();
return 0;
}
void test()
{
gData = 8+i;
}
|
|
〔リスト26〕test08.s
|
.file "test08.c"
.version "01.01"
gcc2_compiled.:
.globl gData
.data
.align 4
.type gData,@object
.size gData,4
gData:
.long 8
.globl i
.align 4
.type i,@object
.size i,4
i:
.long 1
.text
.align 4
.globl main
.type main,@function
main:
pushl %ebp
movl %esp,%ebp
subl $420,%esp
pushl %ebx
call test
movl i,%eax
addl $8,%eax
movl %eax,gData
movl $0,i
.p2align 4,,7
.L3:
cmpl $99,i
jle .L6
jmp .L4
.p2align 4,,7
.L6:
movl i,%eax
movl %eax,%edx
leal 0(,%edx,4),%eax
leal -400(%ebp),%edx
movl i,%ebx
movl %ebx,%ecx
sall $2,%ecx
addl %ebx,%ecx
leal 0(,%ecx,4),%ebx
movl %ebx,(%eax,%edx)
.L5:
incl i
jmp .L3
.p2align 4,,7
.L4:
movl i,%eax
addl $8,%eax
movl %eax,gData
call test
xorl %eax,%eax
jmp .L2
.p2align 4,,7
.L2:
movl -424(%ebp),%ebx
movl %ebp,%esp
popl %ebp
ret
.Lfe1:
.size main,.Lfe1-main
.align 4
.globl test
.type test,@function
test:
pushl %ebp
movl %esp,%ebp
movl i,%eax
addl $8,%eax
movl %eax,gData
.L7:
movl %ebp,%esp
popl %ebp
ret
.Lfe2:
.size test,.Lfe2-test
.ident "GCC: (GNU) 2.95.3 20010315 (release)"
|
|
〔リスト27〕test08.s(-fgcse)
|
.file "test08.c"
.version "01.01"
gcc2_compiled.:
.globl gData
.data
.align 4
.type gData,@object
.size gData,4
gData:
.long 8
.globl i
.align 4
.type i,@object
.size i,4
i:
.long 1
.text
.align 4
.globl test
.type test,@function
test:
pushl %ebp
movl i,%eax
movl %esp,%ebp
addl $8,%eax
movl %eax,gData
movl %ebp,%esp
popl %ebp
ret
.Lfe1:
.size test,.Lfe1-test
.align 4
.globl main
.type main,@function
main:
pushl %ebp
movl %esp,%ebp
subl $420,%esp
pushl %ebx
call test
movl i,%eax
addl $8,%eax
movl %eax,gData
movl $0,i
leal -400(%ebp),%ebx
.p2align 4,,7
.L21:
movl i,%ecx
leal 0(,%ecx,4),%eax
leal (%ecx,%eax),%edx
sall $2,%edx
movl %edx,(%eax,%ebx)
leal 1(%ecx),%eax
movl %eax,i
cmpl $99,%eax
jle .L21
addl $9,%ecx
movl %ecx,gData
call test
xorl %eax,%eax
popl %ebx
movl %ebp,%esp
popl %ebp
ret
.Lfe2:
.size main,.Lfe2-main
.ident "GCC: (GNU) 2.95.3 20010315 (release)"
|
|
この例では何度も繰り返されるgData = 8+i;をひとまとめにセットしています.8+iの値も不変なのでまとめられます.
● -foptimize-register-moves,-fregmove
このオプションは,2オペランド命令の機種で役立ちます.インテルアーキテクチャのマシンではあまり意味をもちません.
TRON仕様やRISC系マイクロプロセッサをもつマシンに指定してレジスタの割り当てを最適化します.TRON仕様のマシンの32ビット仕様のプロセッサには32ビット汎用レジスタが16あるので,効率よく変数をレジスタに割り当てられます.
● -ffunction-sections,-fdata-sections
このオプションもまた,インテルアーキテクチャのマシンではあまり意味をもちません.
リンカに命令空間の参照の局所性を改善する最適化を行う機能があるシステムでは,これらのオプションを使うと良いと思います.HP-UXの稼働するHP PAプロセッサとSolaris2の稼働するSPARCプロセッサには,このような最適化機能のあるリンカが存在します.このオプションを使うとデバッグ時に問題が生じる可能性があるので,デバッグ中には使用しないほうが良いと思います.しかし,付けたときと付けないときで実行形式ファイルが変わるため,試験を慎重に行わないとバグの元になるおそれがあります.
● -fcaller-saves
関数呼び出しにより破壊されるレジスタに値を割り当てることを可能にしますが.その結果として,命令を余分に生成し,呼び出しの前後でレジスタのセーブとリストアを行うので汎用レジスタがふんだんにあるマシンでないと意味がないように思います.
● -funroll-loops
ループ展開最適化を実行します.これは,コンパイル時か実行時に繰り返し回数が決められるループにしか行われません.
-funroll-loopsは,前述の-fstrength-reduceと-frerun-cse-after-loopを含みます.
● -funroll-all-loops
ループ展開最適化を実行します.これは,すべてのループに対して行われ,普通はプログラムの実行を遅くしてしまいます.
● -fmove-all-movables
ループ中の不変な計算をすべてループの外に移動します.
● -freduce-all-givs
ループ中の一般誘導変数を削減します.
最適化というものは両刃の剣であり,あちらを立てればこちらが立たずという状態にもなります.せっかく速度を追求しても間違った最適化を行うと,サイズは小さくなったものの速度が遅くなってしまうようなこともよくある話です.
このオプションを使う際は,皆さんも試行錯誤してみてください.その手間がこの記事で少しでも省ければ幸いです.
|