1. ホーム
  2. c++

[解決済み] while (1) Vs. for (;;) 速度差はあるのか?

2022-04-25 09:16:03

質問

ロングバージョン...

の使い方を見て、同僚が断言しました。 while (1) はPerlスクリプトで for (;;) の方が速い。 私は、インタープリタがその違いを最適化してくれることを期待して、両者は同じであるべきだと主張しました。そこで、1,000,000,000回のforループと同じ回数のwhileループを実行し、その間の時間を記録するスクリプトを作成しました。その結果、有意な差は見つからなかった。私の同僚は、ある教授から while (1) は比較をしていた 1 == 1 と、その for (;;) はなかった。 C++で100倍の反復計算をして同じテストを繰り返しましたが、その差はごくわずかでした。しかし、コンパイルされたコードがスクリプト言語に比べてどれだけ高速化できるかを示す、画期的な例となりました。

ショートバージョン...

を好む理由はあるのでしょうか? while (1) の上に for (;;) 無限ループから抜け出したい場合は?

注意してください。 質問からわからないのであれば これは純粋に友人カップルの楽しい学問的な議論でした。 すべてのプログラマーが悩むような超重要なコンセプトではないことは承知しています。 この議論から私は(そしてきっと他の人も)いくつかのことを学んだと思います。

更新しました。 前述の同僚は、以下のように回答しています。

埋もれてしまうかもしれないので、ここに引用しておきます。

<ブロッククオート

AMDのアセンブリプログラマーからの情報です。彼は、Cプログラマーは (そのコードが非効率的であることに気づいていないのです。彼はこう言っています。 しかし、現在ではgccコンパイラが非常に優秀で、彼のような人を排除しています。 ということです。例えば、と言って教えてくれたのは while 1for(;;) . 今は習慣的に使っていますが、gccや特にインタプリタでは は、最近はどちらも同じ操作(プロセッサジャンプ)をしてくれます。 最適化されているからです。

解決方法は?

perlでは、同じopcodeになります。

$ perl -MO=Concise -e 'for(;;) { print "foo\n" }'
a  <@> leave[1 ref] vKP/REFC ->(end)
1     <0> enter ->2
2     <;> nextstate(main 2 -e:1) v ->3
9     <2> leaveloop vK/2 ->a
3        <{> enterloop(next->8 last->9 redo->4) v ->4
-        <@> lineseq vK ->9
4           <;> nextstate(main 1 -e:1) v ->5
7           <@> print vK ->8
5              <0> pushmark s ->6
6              <$> const[PV "foo\n"] s ->7
8           <0> unstack v ->4
-e syntax OK

$ perl -MO=Concise -e 'while(1) { print "foo\n" }'
a  <@> leave[1 ref] vKP/REFC ->(end)
1     <0> enter ->2
2     <;> nextstate(main 2 -e:1) v ->3
9     <2> leaveloop vK/2 ->a
3        <{> enterloop(next->8 last->9 redo->4) v ->4
-        <@> lineseq vK ->9
4           <;> nextstate(main 1 -e:1) v ->5
7           <@> print vK ->8
5              <0> pushmark s ->6
6              <$> const[PV "foo\n"] s ->7
8           <0> unstack v ->4
-e syntax OK

同様にGCCでも。

#include <stdio.h>

void t_while() {
    while(1)
        printf("foo\n");
}

void t_for() {
    for(;;)
        printf("foo\n");
}

    .file   "test.c"
    .section    .rodata
.LC0:
    .string "foo"
    .text
.globl t_while
    .type   t_while, @function
t_while:
.LFB2:
    pushq   %rbp
.LCFI0:
    movq    %rsp, %rbp
.LCFI1:
.L2:
    movl    $.LC0, %edi
    call    puts
    jmp .L2
.LFE2:
    .size   t_while, .-t_while
.globl t_for
    .type   t_for, @function
t_for:
.LFB3:
    pushq   %rbp
.LCFI2:
    movq    %rsp, %rbp
.LCFI3:
.L5:
    movl    $.LC0, %edi
    call    puts
    jmp .L5
.LFE3:
    .size   t_for, .-t_for
    .section    .eh_frame,"a",@progbits
.Lframe1:
    .long   .LECIE1-.LSCIE1
.LSCIE1:
    .long   0x0
    .byte   0x1
    .string "zR"
    .uleb128 0x1
    .sleb128 -8
    .byte   0x10
    .uleb128 0x1
    .byte   0x3
    .byte   0xc
    .uleb128 0x7
    .uleb128 0x8
    .byte   0x90
    .uleb128 0x1
    .align 8
.LECIE1:
.LSFDE1:
    .long   .LEFDE1-.LASFDE1
.LASFDE1:
    .long   .LASFDE1-.Lframe1
    .long   .LFB2
    .long   .LFE2-.LFB2
    .uleb128 0x0
    .byte   0x4
    .long   .LCFI0-.LFB2
    .byte   0xe
    .uleb128 0x10
    .byte   0x86
    .uleb128 0x2
    .byte   0x4
    .long   .LCFI1-.LCFI0
    .byte   0xd
    .uleb128 0x6
    .align 8
.LEFDE1:
.LSFDE3:
    .long   .LEFDE3-.LASFDE3
.LASFDE3:
    .long   .LASFDE3-.Lframe1
    .long   .LFB3
    .long   .LFE3-.LFB3
    .uleb128 0x0
    .byte   0x4
    .long   .LCFI2-.LFB3
    .byte   0xe
    .uleb128 0x10
    .byte   0x86
    .uleb128 0x2
    .byte   0x4
    .long   .LCFI3-.LCFI2
    .byte   0xd
    .uleb128 0x6
    .align 8
.LEFDE3:
    .ident  "GCC: (Ubuntu 4.3.3-5ubuntu4) 4.3.3"
    .section    .note.GNU-stack,"",@progbits

つまり、多くのコンパイラで同じということですね。もちろん、他のコンパイラでは必ずしもそうではないかもしれませんが、ループの中のコードはループ自体よりも数千倍も高価になる可能性がありますから、誰が気にするでしょうか?