while (1) Vs. for (;;) Is there a speed difference?

Copas picture Copas · May 20, 2009 · Viewed 51.6k times · Source

Long version...

A co-worker asserted today after seeing my use of while (1) in a Perl script that for (;;) is faster. I argued that they should be the same hoping that the interpreter would optimize out any differences. I set up a script that would run 1,000,000,000 for loop iterations and the same number of while loops and record the time between. I could find no appreciable difference. My co-worker said that a professor had told him that the while (1) was doing a comparison 1 == 1 and the for (;;) was not. We repeated the same test with the 100x the number of iterations with C++ and the difference was negligible. It was however a graphic example of how much faster compiled code can be vs. a scripting language.

Short version...

Is there any reason to prefer a while (1) over a for (;;) if you need an infinite loop to break out of?

Note: If it's not clear from the question. This was purely a fun academic discussion between a couple of friends. I am aware this is not a super important concept that all programmers should agonize over. Thanks for all the great answers I (and I'm sure others) have learned a few things from this discussion.

Update: The aforementioned co-worker weighed in with a response below.

Quoted here in case it gets buried.

It came from an AMD assembly programmer. He stated that C programmers (the poeple) don't realize that their code has inefficiencies. He said today though, gcc compilers are very good, and put people like him out of business. He said for example, and told me about the while 1 vs for(;;). I use it now out of habit but gcc and especially interpreters will do the same operation (a processor jump) for both these days, since they are optimized.

Answer

bdonlan picture bdonlan · May 20, 2009

In perl, they result in the same opcodes:

$ 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

Likewise in 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

So I guess the answer is, they're the same in many compilers. Of course, for some other compilers this may not necessarily be the case, but chances are the code inside of the loop is going to be a few thousand times more expensive than the loop itself anyway, so who cares?