次の単純なコードを使用していくつかの実験を行って、if-else
構造を最適化するときにコンパイラが何をするかを調べています。私が使用しているコードは
#include <stdio.h>
int main() {
int arr[] = {1,2,3,4,5,6,7};
int i;
for(i = 0; i < 5; i++) {
if(arr[i] == 1)
printf("one\n");
else if (arr[i] == 2)
printf("two\n");
else if (arr[i] = 3)
printf("three\n");
else printf("blah\n");
}
return 0;
}
確かにあまり良い例ではありません。ここには、可能性の高いブランチとそうでないブランチを区別するための動的なものは何もないためです。
しかし、驚いたことに、生成されたコードは大きく異なります。
最初に最適化なしで私は持っています:
0x0000000000400506 <+66>: mov eax,DWORD PTR [rbp-0x4]
0x0000000000400509 <+69>: cdqe
0x000000000040050b <+71>: mov eax,DWORD PTR [rbp+rax*4-0x20]
0x000000000040050f <+75>: cmp eax,0x1
0x0000000000400512 <+78>: jne 0x400520 <main+92>
0x0000000000400514 <+80>: mov edi,0x400668
0x0000000000400519 <+85>: call 0x4003b8 <puts@plt>
0x000000000040051e <+90>: jmp 0x400551 <main+141>
0x0000000000400520 <+92>: mov eax,DWORD PTR [rbp-0x4]
0x0000000000400523 <+95>: cdqe
0x0000000000400525 <+97>: mov eax,DWORD PTR [rbp+rax*4-0x20]
0x0000000000400529 <+101>: cmp eax,0x2
0x000000000040052c <+104>: jne 0x40053a <main+118>
0x000000000040052e <+106>: mov edi,0x40066c
0x0000000000400533 <+111>: call 0x4003b8 <puts@plt>
0x0000000000400538 <+116>: jmp 0x400551 <main+141>
0x000000000040053a <+118>: mov eax,DWORD PTR [rbp-0x4]
0x000000000040053d <+121>: cdqe
0x000000000040053f <+123>: mov DWORD PTR [rbp+rax*4-0x20],0x3
0x0000000000400547 <+131>: mov edi,0x400670
0x000000000040054c <+136>: call 0x4003b8 <puts@plt>
コードはかなり簡単です。シーケンシャルcmp
であり、予想どおり構造jne
の心臓部です。if-else
しかし、楽しみは ( -O3
) から始まります
0x0000000000400510 <+64>: call 0x4003b8 <puts@plt>
0x0000000000400515 <+69>: mov eax,DWORD PTR [rsp+0x4]
0x0000000000400519 <+73>: cmp eax,0x1
0x000000000040051c <+76>: je 0x400640 <main+368>
0x0000000000400522 <+82>: cmp eax,0x2
0x0000000000400525 <+85>: je 0x4005a0 <main+208>
0x0000000000400527 <+87>: mov edi,0x40074c
0x000000000040052c <+92>: mov DWORD PTR [rsp+0x4],0x3
0x0000000000400534 <+100>: call 0x4003b8 <puts@plt>
0x0000000000400539 <+105>: mov eax,DWORD PTR [rsp+0x8]
0x000000000040053d <+109>: cmp eax,0x1
0x0000000000400540 <+112>: je 0x4005b3 <main+227>
0x0000000000400542 <+114>: cmp eax,0x2
0x0000000000400545 <+117>: je 0x400630 <main+352>
0x000000000040054b <+123>: mov edi,0x40074c
0x0000000000400550 <+128>: mov DWORD PTR [rsp+0x8],0x3
0x0000000000400558 <+136>: call 0x4003b8 <puts@plt>
0x000000000040055d <+141>: mov eax,DWORD PTR [rsp+0xc]
0x0000000000400561 <+145>: cmp eax,0x1
0x0000000000400564 <+148>: je 0x4005d0 <main+256>
0x0000000000400566 <+150>: cmp eax,0x2
0x0000000000400569 <+153>: je 0x400618 <main+328>
0x000000000040056f <+159>: mov edi,0x40074c
0x0000000000400574 <+164>: mov DWORD PTR [rsp+0xc],0x3
0x000000000040057c <+172>: call 0x4003b8 <puts@plt>
0x0000000000400581 <+177>: mov eax,DWORD PTR [rsp+0x10]
0x0000000000400585 <+181>: cmp eax,0x1
0x0000000000400588 <+184>: je 0x4005e8 <main+280>
0x000000000040058a <+186>: cmp eax,0x2
0x000000000040058d <+189>: je 0x400600 <main+304>
0x000000000040058f <+191>: mov edi,0x40074c
0x0000000000400594 <+196>: call 0x4003b8 <puts@plt>
0x0000000000400599 <+201>: xor eax,eax
0x000000000040059b <+203>: add rsp,0x28
0x000000000040059f <+207>: ret
0x00000000004005a0 <+208>: mov edi,0x400752
0x00000000004005a5 <+213>: call 0x4003b8 <puts@plt>
0x00000000004005aa <+218>: mov eax,DWORD PTR [rsp+0x8]
0x00000000004005ae <+222>: cmp eax,0x1
0x00000000004005b1 <+225>: jne 0x400542 <main+114>
0x00000000004005b3 <+227>: mov edi,0x400748
0x00000000004005b8 <+232>: call 0x4003b8 <puts@plt>
0x00000000004005bd <+237>: mov eax,DWORD PTR [rsp+0xc]
0x00000000004005c1 <+241>: cmp eax,0x1
0x00000000004005c4 <+244>: jne 0x400566 <main+150>
0x00000000004005c6 <+246>: nop WORD PTR cs:[rax+rax*1+0x0]
0x00000000004005d0 <+256>: mov edi,0x400748
0x00000000004005d5 <+261>: call 0x4003b8 <puts@plt>
0x00000000004005da <+266>: mov eax,DWORD PTR [rsp+0x10]
0x00000000004005de <+270>: cmp eax,0x1
0x00000000004005e1 <+273>: jne 0x40058a <main+186>
0x00000000004005e3 <+275>: nop DWORD PTR [rax+rax*1+0x0]
0x00000000004005e8 <+280>: mov edi,0x400748
0x00000000004005ed <+285>: call 0x4003b8 <puts@plt>
0x00000000004005f2 <+290>: xor eax,eax
0x00000000004005f4 <+292>: add rsp,0x28
0x00000000004005f8 <+296>: ret
0x00000000004005f9 <+297>: nop DWORD PTR [rax+0x0]
0x0000000000400600 <+304>: mov edi,0x400752
0x0000000000400605 <+309>: call 0x4003b8 <puts@plt>
0x000000000040060a <+314>: xor eax,eax
0x000000000040060c <+316>: add rsp,0x28
0x0000000000400610 <+320>: ret
0x0000000000400611 <+321>: nop DWORD PTR [rax+0x0]
0x0000000000400618 <+328>: mov edi,0x400752
0x000000000040061d <+333>: call 0x4003b8 <puts@plt>
0x0000000000400622 <+338>: jmp 0x400581 <main+177>
0x0000000000400627 <+343>: nop WORD PTR [rax+rax*1+0x0]
0x0000000000400630 <+352>: mov edi,0x400752
0x0000000000400635 <+357>: call 0x4003b8 <puts@plt>
0x000000000040063a <+362>: jmp 0x40055d <main+141>
0x000000000040063f <+367>: nop
0x0000000000400640 <+368>: mov edi,0x400748
0x0000000000400645 <+373>: call 0x4003b8 <puts@plt>
0x000000000040064a <+378>: jmp 0x400539 <main+105>
ここで注意すべき重要事項:
コード内を移動するための無条件のジャンプがたくさんあります。
je
の代わりに使用しjne
ます。
重複したコード領域がたくさんあります。との比較1
が複数回行われます。
最適化されたアセンブラーをさらに掘り下げて、興味深い発見があればこの記事を更新し続けます。これはそれほど多くの答えではありませんが、重要な最適化プラクティスを見つけるために同様の種類の調査を行うための調査であり、他の人への招待でもあります。
編集:
コンパイラ情報:
[root@s1 ~]# gcc --version
gcc (GCC) 4.4.6 20110731 (Red Hat 4.4.6-3)
最適化情報:
-O2 は、次の最適化フラグをオンにします。
-fthread-jumps
-falign-functions -falign-jumps
-falign-loops -falign-labels
-fcaller-saves
-fcrossjumping
-fcse-follow-jumps -fcse-skip-blocks
-fdelete-null-pointer-checks
-fdevirtualize
-fexpensive-optimizations
-fgcse -fgcse-lm
-fhoist-adjacent-loads
-finline-small-functions
-findirect-inlining
-fipa-sra
-foptimize-sibling-calls
-fpartial-inlining
-fpeephole2
-fregmove
-freorder-blocks -freorder-functions
-frerun-cse-after-loop
-fsched-interblock -fsched-spec
-fschedule-insns -fschedule-insns2
-fstrict-aliasing -fstrict-overflow
-ftree-switch-conversion -ftree-tail-merge
-ftree-pre
-ftree-vrp
-O3 は -O2 で追加の最適化を追加します:
-finline-functions, -funswitch-loops, -fpredictive-commoning, -fgcse-after-reload, -ftree-vectorize, -fvect-cost-model, -ftree-partial-pre and -fipa-cp-clone
if-else
ブロック関連の最適化:
-fcse-follow-jumps 共通部分式除去 (CSE) で、ジャンプのターゲットが他のパスによって到達されない場合に、ジャンプ命令をスキャンします。たとえば、CSE が else 句を含む if ステートメントに遭遇した場合、テストされた条件が false の場合、CSE はジャンプに従います。
-fcse-skip-blocks これは -fcse-follow-jumps に似ていますが、条件付きでブロックをスキップするジャンプを CSE に追従させます。CSE が、else 句のない単純な if ステートメントに遭遇すると、-fcse-skip-blocks により、CSE は if の本文のジャンプをたどります。
fhoist-adjacent-loads ロードが同じ構造内の隣接する場所からのもので、ターゲット アーキテクチャに条件付き移動命令がある場合、if-then-else の両方の分岐から投機的にロードをホイストします。このフラグは、-O2 以降でデフォルトで有効になります。