So let's run an experiment, shall we. Given Count = x.Length = 10000000 in C# (.NET, but the GC will never run):
Code:
for (var i = 0; i < Count; i += 1)
if (i % 2 == 0)
x[i] = 0;
else x[i] = 1;
Time: 55-80 millisecondsNow the same functionality in two non-branching loops:
for (var i = 0; i < Count; i += 2)
x[i] = 0;
for (var i = 1; i < Count; i += 2)
x[i] = 1;
About 25-28 milliseconds. Now, to make it interesting, we do zero for the first half and one for the second half: for (var i = 0; i < Count; i += 1)
if (i < Count / 2)
x[i] = 0;
else x[i] = 1;
40-42 milliseconds. Definitely, a coherent branch is much faster than an incoherent one, while no branching wins the day as always. (ok, not true, see edit).Edit: so I was probably running into some jitter problems. Running the tests 20 times gives me 41 for the first, 21 for the second, and 39 for the last. So the first and last are comparable in time. Sorry for the sloppy benchmarking!
Edit 2: so if I take the Count/2 out of the loop, my numbers for the third test become fast again: 42 for the first, 19 for the second, 26 for the third (average time milliseconds for doing the loops 10 times). The comparison is still hardly fair since I'm not lifting the modulo out of the first loop, however. If I use a boolean that is continuously negated instead the modulo, the results are 29 for the first, which is much closer to the third.
Benchmarking on a particular CPU does give you more precise results, but at the risk of them being less generally applicable.
You response encouraged me to check whether the branch predictor in cachegrind had been updated since I last looked at it. It doesn't look like it. It's still pretty simple, about 20 lines of actual code: https://github.com/fredericgermain/valgrind/blob/master/cach...
I greatly appreciated the honesty in one of the comments: "TODO: use predictor written by someone who understands this stuff."
It is a valid question might whether the consistency from processor to processor is any better than cachegrind or a rule of thumb. My experience was that for Nehalem/Sandy Bridge/Haswell things were more same than different (and only got better), but I don't know about other lines.
First, modulo oscillation:
var b = true;
for (var i = 0; i < x.Length; i += 1)
{
var cc = bb[i];
if (b)
x[i] = 0;
else
x[i] = 1;
b = !b;
}
Time: 36 milliseconds (averaged over 10 runs)Case 2, no branch:
for (var i = 0; i < x.Length; i += 2)
{
var c = bb[i];
x[i] = 0;
}
for (var i = 1; i < x.Length; i += 2)
{
var c = bb[i];
x[i] = 1;
}
Time: 22 millisecondsCase 3, coherent branching:
var kk = x.Length / 2;
for (var i = 0; i < x.Length; i += 1)
{
var c = bb[i];
if (i < kk)
x[i] = 0;
else x[i] = 1;
}
Time: 31 millisecondsCase 4, totally random branching:
for (var i = 0; i < x.Length; i += 1)
if (bb[i])
x[i] = 0;
else x[i] = 1;
Time: 75 milliseconds (the slowness could come from bad branch prediction, or the fact that a load needs to complete before the condition can be verified!)