Is "For(;;)" Faster Than "While (True)"? If Not, Why Do People Use It

Is for(;;) faster than while (true)? If not, why do people use it?

  1. It's not faster.
  2. If you really care, compile with assembler output for your platform and look to see.
  3. It doesn't matter. This never matters. Write your infinite loops however you like.

What is better for(;;) {} or while(true) {}?

From a performance point of view it doesn't matter. The compiler will optimize both into the same assembly code. From a "coding standards" point of view, for(;;) is more terse, which is nice. Although while(TRUE) is a bit clearer, for(;;) is so common that clarity in this case is not much of an issue (in fact, the ubiquity of for(;;) may make it more clear than the while version).

while(true) versus for(;;)

With optimizations enabled, they will compile identically.

You should use whichever one you find more readable.

Why is = slower than using this code snippet in V8?

I work on V8 at Google, and wanted to provide some additional insight on top of the existing answers and comments.

For reference, here's the full code example from the slides:

var iterations = 25000;

function Primes() {
this.prime_count = 0;
this.primes = new Array(iterations);
this.getPrimeCount = function() { return this.prime_count; }
this.getPrime = function(i) { return this.primes[i]; }
this.addPrime = function(i) {
this.primes[this.prime_count++] = i;
}
this.isPrimeDivisible = function(candidate) {
for (var i = 1; i <= this.prime_count; ++i) {
if ((candidate % this.primes[i]) == 0) return true;
}
return false;
}
};

function main() {
var p = new Primes();
var c = 1;
while (p.getPrimeCount() < iterations) {
if (!p.isPrimeDivisible(c)) {
p.addPrime(c);
}
c++;
}
console.log(p.getPrime(p.getPrimeCount() - 1));
}

main();

First and foremost, the performance difference has nothing to do with the < and <= operators directly. So please don't jump through hoops just to avoid <= in your code because you read on Stack Overflow that it's slow --- it isn't!


Second, folks pointed out that the array is "holey". This was not clear from the code snippet in OP's post, but it is clear when you look at the code that initializes this.primes:

this.primes = new Array(iterations);

This results in an array with a HOLEY elements kind in V8, even if the array ends up completely filled/packed/contiguous. In general, operations on holey arrays are slower than operations on packed arrays, but in this case the difference is negligible: it amounts to 1 additional Smi (small integer) check (to guard against holes) each time we hit this.primes[i] in the loop within isPrimeDivisible. No big deal!

TL;DR The array being HOLEY is not the problem here.


Others pointed out that the code reads out of bounds. It's generally recommended to avoid reading beyond the length of arrays, and in this case it would indeed have avoided the massive drop in performance. But why though? V8 can handle some of these out-of-bound scenarios with only a minor performance impact. What's so special about this particular case, then?

The out-of-bounds read results in this.primes[i] being undefined on this line:

if ((candidate % this.primes[i]) == 0) return true;

And that brings us to the real issue: the % operator is now being used with non-integer operands!

  • integer % someOtherInteger can be computed very efficiently; JavaScript engines can produce highly-optimized machine code for this case.

  • integer % undefined on the other hand amounts to a way less efficient Float64Mod, since undefined is represented as a double.

The code snippet can indeed be improved by changing the <= into < on this line:

for (var i = 1; i <= this.prime_count; ++i) {

...not because <= is somehow a superior operator than <, but just because this avoids the out-of-bounds read in this particular case.

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

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?

If Statement Vs. while loop

It is generally not faster in natively compiled languages like C, C++, Rust, etc. since the optimizing compilers can rewrite this anyway but basic one may not (resulting in more branches. It is bug prone due to the break and does not make the intent clear resulting in a less readable code. Put it shortly: do not use this code to emulate conditions. Instead, write clean codes, check that the compiler does its optimizing job correctly, and only then, use micro-optimization to speed up your code if your profiler show this is needed and worth it. As Donald Knuth said once: Premature optimization is the root of all evil.

python: while True vs while (condition) efficiency

I put those snippets in two functions:

def first():
a=100000000
while a > 0:
a-=10

def second():
a=100000000
while True:
a-=10
if a <= 0:
break

And then used timeit.timeit and it was actually the second that was consistently timed as taking longer (although for really negligible difference).

from timeit import timeit

timeit(first, number=100)
timeit(second, number=100)

I then inspected the bytecode by basically copying the example from the dis docs. This revealed that the bytecode is almost the same for both snippets, the only difference being in how the opcodes are ordered and that the second includes one additional instruction, namely BREAK_LOOP.

import dis
from collections import Counter

first_bytecode = dis.Bytecode(first)
for instr in first_bytecode:
print(instr.opname)

second_bytecode = dis.Bytecode(second)
for instr in second_bytecode:
print(instr.opname)

# easier to compare with Counters
first_b = Counter(x.opname for x in first_bytecode)
sec_b = Counter(x.opname for x in second_bytecode)

Finally, one might think the order might make a difference (and it could), but in order to make a fair comparison second should first check for the break condition before subtracting from a. Consider then a third function:

def third():
a=100000000
while True:
if a <= 0:
break
a-=10

Comparing the bytecode of the third you'll see it is in fact ordered the same as the bytecode of the first, the only difference being a single BREAK_LOOP jammed somewhere in the middle.

If anything I hope this shows you how insignificant execution of one opcode usually is with respect to the overall performance of code. I think the immortal words of Donald Knuth are particularly well suited for the occasion:

We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.

for (;;) is this an infinite loop?

Yes, it's infinite. Traditionally, compilers would generate a warning when you use while(1), but not when you use for(;;). I don't know if this is still the case.



Related Topics



Leave a reply



Submit