より高度な最適化オプション

 最適化オプションはまだあります.次は,より高度な最適化を行いたいという場合に使うものです.

 上記のオプションは前号で説明したものの検証ですが,次は検証と説明を同時に行います.

-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

 ループ中の一般誘導変数を削減します.

 最適化というものは両刃の剣であり,あちらを立てればこちらが立たずという状態にもなります.せっかく速度を追求しても間違った最適化を行うと,サイズは小さくなったものの速度が遅くなってしまうようなこともよくある話です.

 このオプションを使う際は,皆さんも試行錯誤してみてください.その手間がこの記事で少しでも省ければ幸いです.

記事内インデックス    連載インデックスはこちら   Interfaceのトップ
最適化オプションの出力
より高度な最適化オプション
出力の制御オプション

Copyright 2002 岸 哲夫

Copyright 1997-2017 CQ Publishing Co.,Ltd.