3

(C) if を含む関数がある場合、条件が true の場合は特定の値を返し、それ以外の場合は別の値を返すことができます。else を使用することは多かれ少なかれ効率的ですか?

つまり...

int foo (int a) {
    if ((a > 0) && (a < SOME_LIMIT)) {
        b = a //maybe b is some global or something
        return 0;
    } else {
        return 1;
    }
    return 0;
}

あるいは単に

int foo (int a) {
    if ((a > 0) && (a < SOME_LIMIT)) {
        b = a //maybe b is some global or something
        return 0;
    }
    return 1;
}

GCC を想定すると、最初の実装では、コンパイルされたコードが 2 番目の実装とは異なる結果になるでしょうか?

ここでは可能な限り効率的である必要があるため、else の分岐の可能な削減は素晴らしいことですが、スタイル的には、私の内部 OCD は、関数の最後の命令として 0 または void ではない戻り値を表示することを好みません。何が起こっているのかあまり明確ではありません。とにかくそれが取り除かれるなら、私は他のものをそこに残すことができます...

4

6 に答える 6

8

最適化されたアセンブリ コードを生成するオプションを指定して gcc を実行すると、-O3 -S最適化されたアセンブリを表示 (および比較) できます。ソースをコンパイルできるように、ソースに次の変更を加えました。

ファイルac :

int b;                                                                         

int foo (int a) {             
    if ((a > 0) && (a < 5000)) {  
        b = a;                                                    
        return 0;                                                        
    } else {                                                                   
        return 1;             
    }                                       
    return 0;                          
}

ファイルbc :

int b;                                                                         
int foo (int a) {                                                             
    if ((a > 0) && (a < 5000)) {
        b = a;
        return 0;
    }       
    return 1;                                                                  
}

作成されたファイルを使用してacをコンパイルする場合。私のマシンでは、次のようになります。gcc -O3 -S a.c

               .file      "a.c"
               .text
               .p2align 4,,15
               .globl     foo
               .type      foo, @function
foo:
.LFB0:
               .cfi_startproc
               movl       4(%esp), %edx
               movl       $1, %eax
               leal       -1(%edx), %ecx
               cmpl       $4998, %ecx
               ja         .L2
               movl       %edx, b
               xorb       %al, %al
.L2:
               rep
               ret
               .cfi_endproc
.LFE0:
               .size      foo, .-foo
               .comm      b,4,4
               .ident     "GCC: (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1"
               .section   .note.GNU-stack,"",@progbits

ファイルbsでbcをコンパイルすると、作成されます。私のマシンでは、次のようになります。gcc -O3 -S b.c

               .file      "b.c"
               .text
               .p2align 4,,15
               .globl     foo
               .type      foo, @function
foo:
.LFB0:
               .cfi_startproc
               movl       4(%esp), %edx
               movl       $1, %eax
               leal       -1(%edx), %ecx
               cmpl       $4998, %ecx
               ja         .L2
               movl       %edx, b
               xorb       %al, %al
.L2:
               rep
               ret
               .cfi_endproc
.LFE0:
               .size      foo, .-foo
               .comm      b,4,4
               .ident     "GCC: (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1"
               .section   .note.GNU-stack,"",@progbits

のアセンブルされた実装foo:は同一であることに注意してください。したがって、この場合、このバージョンの GCC では、コードの記述方法は問題になりません。

于 2013-07-10T15:29:49.497 に答える
1

両方の実装間でオブジェクト ファイルを確認してください。次のようなアセンブリ ヘッダーを配置します。

 PortionInQuestion: 

これはアセンブリ ファイルにラベルとして表示され、生成されたアセンブリがどのように異なるかを確認できます。それらは (最適化のために) まったく異なる場合もあれば、完全に異なる場合もあります。生のアセンブリを見なければ、コンパイラがそれをどのように最適化しているかを知る方法はありません。

于 2013-07-10T15:15:21.397 に答える
1
int foo (int a) {

     /* Nothing to do: get out of here */
    if (a <= 0 || a >= SOME_LIMIT) return 1;

    b = a; // maybe b is some global or something

    return 0;
}

効率に関してはほとんど違いがありません (最もコストのかかる部分は、とにかく関数呼び出しとリターンです)。

人間の読者にとって、「インデント」が最も少ないコード (上記のようなコード) は、最も読みやすく理解しやすいものです。

ところで、生成されたアセンブラも私にはかなり最小限に見え、フォームと完全に同等if (a >0 && a < LIMIT)です。

        .file   "return.c"
        .text
        .p2align 4,,15
        .globl  foo
        .type   foo, @function
foo:
.LFB0:
        .cfi_startproc
        leal    -1(%rdi), %edx
        movl    $1, %eax
        cmpl    $2998, %edx
        ja      .L2
        movl    %edi, b(%rip)
        xorb    %al, %al
.L2:
        rep
        ret
        .cfi_endproc
.LFE0:
        .size   foo, .-foo
        .globl  b
        .bss
        .align 16
        .type   b, @object
        .size   b, 4
b:
        .zero   4
        .ident  "GCC: (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1"
        .section        .note.GNU-stack,"",@progbits
于 2013-07-10T15:29:55.813 に答える
1

こう書くと・・・

int foo (int a) {
    if ((a > 0) && (a < SOME_LIMIT)) {
        b = a //maybe b is some global or something
        return 0;
    } else {
        return 1;
    }
}

関数全体は、 b の値がゼロより大きく、ある定数より小さいことを条件とする単なるブール値です。そして、if ステートメントはリターンを条件付けます。関数にデフォルトの戻り値を追加する必要はありません。デフォルトの戻り値は if 条件を無効にします。

于 2013-07-10T15:26:50.280 に答える
1

return 0;最初の例では最後まで到達しません。あなたの2番目のものは、少なくともそのため、スタイル的に明確になっていると思います. 通常、同じことのコードが少ないことは良いことです。

パフォーマンスに関しては、必要に応じてアセンブリの実行可能ファイルをチェックアウトするか、コードをプロファイリングして、実際の違いがあるかどうかを確認できます。私の賭けは重要ではありません。

最後に、コンパイラが最適化フラグをサポートしている場合は、それらを使用してください。

于 2013-07-10T15:17:32.287 に答える
1

もちろん、それはコンパイラに依存します。市場に出回っているすべての (まともな) コンパイラは、どちらの場合もまったく同じ出力を生成すると思います。しかし、そのようなマイクロ最適化を試みることは、最適化に関するどの本でも推奨されていません!

より読みやすい形式を選択してください。

于 2013-07-10T15:18:35.660 に答える