(V8 developer here.)
In short, the 1/0 version is faster because the array's elements kind helps the if
statement do less work.
Longer version:
As @PatrickRoberts points out, V8 keeps track of the type of values stored in an array. This mechanism is rather coarse-grained, it only distinguishes between "just integers", "just doubles", and "anything". if(array[i])
, when it knows that the array contains only integers, can simply do a comparison against 0 to see if the branch should be taken. It doesn't get faster than that. If the array contains "anything" (which includes true
), however, then per the semantics of JavaScript, V8 has to check whether the loaded value is "true-ish", i.e. evaluates to true in a conditional context. The opposite, i.e. checking for false-ish values, is actually easier/faster, so V8 checks: is the value false
? Is it ""
? Is it a number (which might be 0)? Is it a BigInt (which might be 0n)? Is it document.all
(a particularly fun special-case relic from ancient times)? Anything else evaluates to true
. In this particular case, it would be "smart"/lucky to check for true
right away, but the engine can't know that, and such a heuristic wouldn't be beneficial in general.
(Note that it would be wrong to conclude that if(1)
is faster than if(true)
-- what matters specifically is that the value in the conditional is loaded from an array, and this array keeps track of the range of possible values, which affects the checks that subsequently need or don't need to be done on a loaded value. When you use constants 1
and true
, then both evaluations have the same speed (in fact, in most situations the optimizing compiler will drop them entirely, because of course if(true)
is true, duh!).)
That said, most of the difference you see isn't due to this, because the test spends more than 90% of its time in the first loop, populating the array. Growing an array from length 0 to a million means its backing store needs to be extended repeatedly, which means a new backing store is allocated and all existing elements are copied over. This is another operation where integer-only elements have a speed benefit: they can use a bulk copying operation, moving data as fast as the CPU can access memory. In an "anything" array, however, the garbage collector must perform an additional pass to see if any of the values are references that are of interest to it. In this case, with all values being the true
sentinel, they're not, but the GC can't know that without checking.