while(1)とwhile(2)のどちらが速いですか? 質問する

while(1)とwhile(2)のどちらが速いですか? 質問する

これは上級管理職が面接で尋ねた質問です。

どちらが速いですか?

while(1) {
    // Some code
}

または

while(2) {
    //Some code
}

内部の式は最終的にまたはwhileに評価されるため、どちらも実行速度は同じであると述べました。この場合、どちらも に評価され、条件内に追加の条件命令はありません。したがって、どちらも実行速度は同じであり、while (1) の方が適しています。truefalsetruewhile

しかし、面接官は自信を持ってこう言いました。「基礎を復習してください。while(1)は よりも速いですwhile(2)。」 (彼は私の自信を試していたわけではありません)

これは本当ですか?

ベストアンサー1

どちらのループも無限ですが、反復ごとにどちらのループでより多くの命令/リソースが必要になるかがわかります。

gcc を使用して、次の 2 つのプログラムをさまざまな最適化レベルでアセンブリにコンパイルしました。

int main(void) {
    while(1) {}
    return 0;
}

int main(void) {
    while(2) {}
    return 0;
}

最適化を行わなくても(-O0)、生成されたアセンブリは両方のプログラムで同一でしたしたがって、 2 つのループ間に速度差はありません。

参考までに、生成されたアセンブリを以下に示します (gcc main.c -S -masm=intel最適化フラグを使用)。

-O0

    .file   "main.c"
    .intel_syntax noprefix
    .def    __main; .scl    2;  .type   32; .endef
    .text
    .globl  main
    .def    main;   .scl    2;  .type   32; .endef
    .seh_proc   main
main:
    push    rbp
    .seh_pushreg    rbp
    mov rbp, rsp
    .seh_setframe   rbp, 0
    sub rsp, 32
    .seh_stackalloc 32
    .seh_endprologue
    call    __main
.L2:
    jmp .L2
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

-O1

    .file   "main.c"
    .intel_syntax noprefix
    .def    __main; .scl    2;  .type   32; .endef
    .text
    .globl  main
    .def    main;   .scl    2;  .type   32; .endef
    .seh_proc   main
main:
    sub rsp, 40
    .seh_stackalloc 40
    .seh_endprologue
    call    __main
.L2:
    jmp .L2
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

および(同じ出力) を使用する-O2場合:-O3

    .file   "main.c"
    .intel_syntax noprefix
    .def    __main; .scl    2;  .type   32; .endef
    .section    .text.startup,"x"
    .p2align 4,,15
    .globl  main
    .def    main;   .scl    2;  .type   32; .endef
    .seh_proc   main
main:
    sub rsp, 40
    .seh_stackalloc 40
    .seh_endprologue
    call    __main
.L2:
    jmp .L2
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

実際、ループ用に生成されたアセンブリは、最適化のレベルに関係なく同一です。

 .L2:
    jmp .L2
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

重要な点は次のとおりです:

.L2:
    jmp .L2

アセンブリをあまりよく読めませんが、これは明らかに無条件ループです。このjmp命令は、値を true と比較することさえせずに、プログラムを無条件にラベルにリセットし.L2、もちろん、プログラムが何らかの方法で終了するまですぐに同じことを繰り返します。これは、C/C++ コードに直接対応しています。

L2:
    goto L2;

編集:

興味深いことに、最適化を行わなくても、次のループはすべてjmpアセンブリでまったく同じ出力 (無条件) を生成しました。

while(42) {}

while(1==1) {}

while(2==2) {}

while(4<7) {}

while(3==3 && 4==4) {}

while(8-9 < 0) {}

while(4.3 * 3e4 >= 2 << 6) {}

while(-0.1 + 02) {}

そしてさらに驚いたことに:

#include<math.h>

while(sqrt(7)) {}

while(hypot(3,4)) {}

ユーザー定義関数を使用すると、状況が少し面白くなります。

int x(void) {
    return 1;
}

while(x()) {}

#include<math.h>

double x(void) {
    return sqrt(7);
}

while(x()) {}

では-O0、これら 2 つの例では実際に を呼び出してx、各反復で比較を実行します。

最初の例(1 を返す):

.L4:
    call    x
    testl   %eax, %eax
    jne .L4
    movl    $0, %eax
    addq    $32, %rsp
    popq    %rbp
    ret
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

2番目の例( を返すsqrt(7)):

.L4:
    call    x
    xorpd   %xmm1, %xmm1
    ucomisd %xmm1, %xmm0
    jp  .L4
    xorpd   %xmm1, %xmm1
    ucomisd %xmm1, %xmm0
    jne .L4
    movl    $0, %eax
    addq    $32, %rsp
    popq    %rbp
    ret
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

ただし、-O1および では、どちらも前の例と同じアセンブリ (jmp前のラベルへの無条件バック) を生成します。

要約

GCC では、異なるループが同一のアセンブリにコンパイルされます。コンパイラは定数値を評価し、実際の比較は実行しません。

この話の教訓は次のとおりです。

  • C ソース コードと CPU 命令の間には変換レイヤーが存在し、このレイヤーはパフォーマンスに重要な影響を及ぼします。
  • したがって、ソースコードだけを見てパフォーマンスを評価することはできません。
  • コンパイラは、このような些細なケースを最適化できるほど賢くなければなりません。ほとんどの場合、プログラマはそれらについて考える時間を無駄にすべきではありません。

おすすめ記事