About the only way to know for sure is to test. I'd have to agree that it would take a fairly clever compiler to produce as efficient of output for:
if(n%2) // or (n%2==0) and flip the order
n=n-1
else
n=n+1
as it could for n ^= 1;
, but I haven't checked anything similar recently enough to say with any certainty.
As for your second question, I doubt it makes any difference -- an equality comparison is going to end up fast for any of these methods. If you want speed, the main thing to do is avoid having a branch involved at all -- e.g. something like:
if (a == b)
c += d;
can be written as: c += d * (a==b);
. Looking at the assembly language, the second will often look a bit messy (with ugly cruft to get the result of the comparison from the flags into a normal register) but still often perform better by avoiding any branches.
Edit: At least the compilers I have handy (gcc & MSVC), do not generate a cmov
for the if
, but they do generate a sete
for the * (a==b)
. I expanded the code to something testable.
Edit2: Since Potatoswatter brought up another possibility using bit-wise and instead of multiplication, I decided to test that along with the others. Here's the code with that added:
#include <time.h>
#include <iostream>
#include <stdlib.h>
int addif1(int a, int b, int c, int d) {
if (a==b)
c+=d;
return c;
}
int addif2(int a, int b, int c, int d) {
return c += d * (a == b);
}
int addif3(int a, int b, int c, int d) {
return c += d & -(a == b);
}
int main() {
const int iterations = 50000;
int x = rand();
unsigned tot1 = 0;
unsigned tot2 = 0;
unsigned tot3 = 0;
clock_t start1 = clock();
for (int i=0; i<iterations; i++) {
for (int j=0; j<iterations; j++)
tot1 +=addif1(i, j, i, x);
}
clock_t stop1 = clock();
clock_t start2 = clock();
for (int i=0; i<iterations; i++) {
for (int j=0; j<iterations; j++)
tot2 +=addif2(i, j, i, x);
}
clock_t stop2 = clock();
clock_t start3 = clock();
for (int i=0; i<iterations; i++) {
for (int j=0; j<iterations; j++)
tot3 +=addif3(i, j, i, x);
}
clock_t stop3 = clock();
std::cout << "Ignore: " << tot1 << "\n";
std::cout << "Ignore: " << tot2 << "\n";
std::cout << "Ignore: " << tot3 << "\n";
std::cout << "addif1: " << stop1-start1 << "\n";
std::cout << "addif2: " << stop2-start2 << "\n";
std::cout << "addif3: " << stop3-start3 << "\n";
return 0;
}
Now the really interesting part: the results for the third version are quite interesting. For MS VC++, we get roughly what most of us would probably expect:
Ignore: 2682925904
Ignore: 2682925904
Ignore: 2682925904
addif1: 4814
addif2: 3504
addif3: 3021
Using the &
instead of the *
, gives a definite improvement -- almost as much of an improvement as *
gives over if
. With gcc the result is quite a bit different though:
Ignore: 2680875904
Ignore: 2680875904
Ignore: 2680875904
addif1: 2901
addif2: 2886
addif3: 7675
In this case, the code using if
is much closer to the speed of the code using *
, but the code using &
is slower than either one -- a lot slower! In case anybody cares, I found this surprising enough that I re-compiled a couple of times with different flags, re-ran a few times with each, and so on and the result was entirely consistent -- the code using &
was consistently considerably slower.
The poor result with the third version of the code compiled with gcc gets us back to what I said to start with [and ends this edit]:
As I said to start with, "the only way to know for sure is to test" -- but at least in this limited testing, the multiplication consistently beats the if
. There may be some combination of compiler, compiler flags, CPU, data pattern, iteration count, etc., that favors the if
over the multiplication -- there's no question that the difference is small enough that a test going the other direction is entirely believable. Nonetheless, I believe that it's a technique worth knowing; for mainstream compilers and CPUs, it seems reasonably effective (though it's certainly more helpful with MSVC than with gcc).
[resumption of edit2:] the result with gcc using &
demonstrates the degree to which 1) micro-optimizations can be/are compiler specific, and 2) how much different real-life results can be from expectations.