The order of Boolean operands
The operands of the Boolean operators && and || are evaluated in the following way. If the first operand of && is false, then the second operand is not evaluated at all because the result is known to be false regardless of the value of the second operand. Likewise, if the first operand of || is true, then the second operand is not evaluated, because the result is known to be true anyway.
It may be advantageous to put the operand that is most often true last in an && expression, or first in an || expression. Assume, for example, that a is true 50% of the time and b is true 10% of the time. The expression a && b needs to evaluate b when a is true, which is 50% of the cases. The equivalent expression b && a needs to evaluate a only when b is true, which is only 10% of the time. This is faster if a and b take the same time to evaluate and are equally likely to be predicted by the branch prediction mechanism. See page 43 for an explanation of branch prediction.
If one operand is more predictable than the other, then put the most predictable operand first.
If one operand is faster to calculate than the other then put the operand that is calculated the fastest first.
However, you must be careful when swapping the order of Boolean operands. You cannot swap the operands if the evaluation of the operands has side effects or if the first operand determines whether the second operand is valid. For example:
// Example 7.7
unsigned int i; const int ARRAYSIZE = 100; float list[ARRAYSIZE]; if (i < ARRAYSIZE && list[i] > 1.0) { ...
Here, you cannot swap the order of the operands because the expression list[i] is invalid when i is not less than ARRAYSIZE. Another example:
// Example 7.8
Here you cannot swap the order of the Boolean operands because you should not call
WriteFile if the handle is invalid.
Boolean variables are overdetermined
Boolean variables are stored as 8-bit integers with the value 0 for false and 1 for true. Boolean variables are overdetermined in the sense that all operators that have Boolean variables as input check if the inputs have any other value than 0 or 1, but operators that have Booleans as output can produce no other value than 0 or 1. This makes operations with Boolean variables as input less efficient than necessary. Take the example:
// Example 7.9a bool a, b, c, d; c = a && b; d = a || b;
This is typically implemented by the compiler in the following way: bool a, b, c, d; if (a != 0) { if (b != 0) { c = 1; } else { goto CFALSE; } } else { CFALSE: c = 0; } if (a == 0) { if (b == 0) { d = 0; } else { goto DTRUE; } } else { DTRUE: d = 1; }
This is of course far from optimal. The branches may take a long time in case of
mispredictions (see page 43). The Boolean operations can be made much more efficient if it is known with certainty that the operands have no other values than 0 and 1. The reason why the compiler does not make such an assumption is that the variables might have other values if they are uninitialized or come from unknown sources. The above code can be optimized if a and b have been initialized to valid values or if they come from operators that produce Boolean output. The optimized code looks like this:
// Example 7.9b
char a = 0, b = 0, c, d; c = a & b;
d = a | b;
Here, I have used char (or int) instead of bool in order to make it possible to use the bitwise operators (& and |) instead of the Boolean operators (&& and ||). The bitwise operators are single instructions that take only one clock cycle. The OR operator (|) works
even if a and b have other values than 0 or 1. The AND operator (&) and the EXCLUSIVE OR operator (^) may give inconsistent results if the operands have other values than 0 and 1.
Note that there are a few pitfalls here. You cannot use ~ for NOT. Instead, you can make a Boolean NOT on a variable which is known to be 0 or 1 by XOR'ing it with 1:
// Example 7.10a bool a, b;
b = !a; can be optimized to:
// Example 7.10b char a = 0, b; b = a ^ 1;
You cannot replace a && b with a & b if b is an expression that should not be evaluated if a is false. Likewise, you cannot replace a || b with a | b if b is an expression that should not be evaluated if a is true.
The trick of using bitwise operators is more advantageous if the operands are variables than if the operands are comparisons, etc. For example:
// Example 7.11
bool a; float x, y, z; a = x > y && z != 0;
This is optimal in most cases. Do not change && to & unless you expect the && expression to generate many branch mispredictions.
Boolean vector operations
An integer may be used as a Boolean vector. For example, if a and b are 32-bit integers, then the expression y = a & b; will make 32 AND-operations in just one clock cycle. The operators &, |, ^, ~ are useful for Boolean vector operations.