1

この質問は、ほとんどすべての C ライクな「中かっこ」プログラミング言語に関連しています。

私は次の使用について話している:

if(condition)
    meh();
else if(condition1)
    bleh();
else if(condition2)
    moo();
else
    foo();

このイディオムをコードで使用する際に注意すべき点はありますか? パフォーマンスのペナルティ、コンパイラの制限などを探しています。典型的なコンパイラはこのようなもので何をしますか?

人間の目にはきれいで平らに見えますが、実際には次のように厳密に解析され、中括弧が追加されているためです。

if(condition)
{
     meh();
}
else
{
    if(condition1)
    {
        bleh();
    }
    else
    {
        //...
    }
}

つまり、else if実際には区切り文字ではありません。代わりに、それぞれifが先行する 内にネストされelseます。x+y+z+... を x+(y+(z+...)) として解析するようなものです。

コンパイラは実際にこのように扱いますか、それともelse if特殊なケースとして扱いますか? 前者の場合、どのような注意が必要ですか?

(これは StackOverflow に関する私の最初の質問です。)

4

4 に答える 4

2

これを見ると。最適化に関する限り、私はこれを頭の中で考えています。

if(blah)
  //Most likely code to be run.
elseif(bleh)
  //Less likely code to be run.
elseif(blarg)
  //Even less likely to be run.
else
  //Almost never ever gets here.

評価内容によっては、switch ステートメントの方が優れている場合があります。

于 2012-07-29T18:57:58.150 に答える
1

言語に関する限り、2 つの構造はまったく同じです。コンパイラは、義務付けられた動作を作成する任意のタイプのマシン コードを自由に作成できます。おそらく、最適化コンパイラは両方のバージョンをまったく同じ方法で処理します。

必要に応じて、コンパイラは繰り返される条件をジャンプ テーブルに置き換えることもできます。繰り返しますが、そのような実装が使用されているかどうかにかかわらず、コードがどのように使用されるかは重要ではありません。(本当に興味がある場合は、アセンブリをコンパイルして比較してください。)

コードの構造が最も明白で明確になるようにコードを記述します。すべてのブランチが対等な立場にある場合、私は個人的にはelse ifs またはswitchステートメントを好みます。

于 2012-07-29T18:56:59.997 に答える
1

次の単純なコードを使用していくつかの実験を行って、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>

ここで注意すべき重要事項:

  1. コード内を移動するための無条件のジャンプがたくさんあります。

  2. jeの代わりに使用しjneます。

  3. 重複したコード領域がたくさんあります。との比較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 以降でデフォルトで有効になります。

于 2012-07-29T19:50:31.683 に答える
1

else-if可能な限りバリアントを使用してください。この構成には欠点がありません。

コンパイラの最適化については何も保証できないのは事実です。同時に、経験と合理的な考え方の両方が、この複雑な構造が現代のコンパイラーにとって問題ではないことを示しています。

于 2012-07-29T19:05:43.913 に答える