Posted tagged ‘optimization’

The Art of Writing Good Conditionals

September 2, 2009

Based on my experience, many of the finer points of writing optimal conditional statements are not covered in most introductory computer science courses. For that reason, I thought I’d go ahead and write a post on the topic. There are other points that I will not be covering, but I’ll offer some advice on how to write great conditionals and I’ll show a few tricks on how to optimize your conditional statements. Keep in mind that conditional statement optimization is a form of micro-optimization and that performance gains from macro-optimization (ie. Good design, optimal data structures and algorithms) will be much greater. The code examples will be written in Java, for concreteness.

Common Mishaps

These are two simple examples of bad practices that should be avoided.

If you have a Boolean b, use it directly! Do not compare it with true or false:

If (b == true) {
//do something

Doing so adds a bit of overhead in your conditional (assuming the compiler doesn’t optimize it out). Instead, just write:

If (b) {
//do something

Use the following table to see the best use of the Boolean b in a conditional.

Expression Better Alternative
b == true / b != false b
b == false / b != true !b

Of course, in order to maintain code clarity, appropriate naming conventions are crucial. Using appropriate names means that one should be able to deduce the meaning of the expression simply by reading it:

if ( !empty && valueFound ) //Expression is easily understandable by reading it

Another bad practice is using the value of a Boolean indirectly:

if (someBooleanExpression) {
return true; //if true return true...
} else {
return false; //if false return false...

Again, just use it directly:

return someBooleanExpression; //use the value directly

Indirect usage of conditionals makes the code unnecessarily long, slightly slower and shows a lack of a true understanding of how Booleans work (or you weren’t paying attention :) , it happens)

Lastly, if you find that you’ve got a large if, else-if structure checking the value of an integral type, use a switch statement instead. Most importantly, switches increase code readability. Also, they are typically better optimized by the compiler and will be anywhere from much faster to roughly the same speed as a large if, else-if structure. The details of the optimization are the topic of a different discussion.

Short-Circuit Evaluation

Hopefully, if you’re reading this post, you are aware of this concept and can skip over this section. Short circuit evaluation occurs when a Boolean statement’s evaluation stops once the overall value of the Boolean is known.  Short circuit evaluation is typically enabled by short-circuit Boolean operators which differ from standard operators. This is the case with C++ and Java:

Standard operators: &, |

Short-circuit operators: &&, ||

This means that in the following two cases, otherExprs will not be evaluated:

if ( falseExpr && (otherExprs) ) //Always false regardless of value of otherExprs

if ( trueExpr || (otherExprs) ) // Always true regardless of value of otherExprs

If you know how to use these operators well, you can use them to write some pretty powerful conditional statements by deliberately short-circuiting in order to:

1. Avoid handling a null object erroneously:

if ( obj != null && obj.doSomething() )  //this ensures that contains is never called on a null object

2. Avoid expensive operations:

if ( value == lastValue || largeMatrix.contains(value) ) // if we can know that the large matrix contains the value without having to call the contains method, we do not need to make the call and can avoid the expensive operation, yes.. not the best example but you get the point

Regular Boolean operators should be avoided. However, in rare cases, they may prove useful: if a Boolean method has a side-effect that is desired regardless of the outcome of the expression then a regular operation may be useful.

if ( func1WithSideEffects() & func2WithSideEffects() ) //this ensures that both functions are always evaluated

Strategies for Conditionals

Finally, I thought I would outline two general strategies that can be used when writing conditional statements in a program. These aren’t hard and fast rules, so they should not be strictly adhered to in all situations. I’ll call the first strategy clarity.

The clarity strategy is an important strategy to follow, it deals with if blocks that handle numerous cases. It consists of placing the valid/good/expected case first, followed by the invalid/bad/exceptional cases.

if ( (x1 > 0 && x1 < 100) && (x2 > 100 && x2 < 200) ) {
//valid input first
} else {
//invalid input, can check what exactly makes it invalid here, or can use else-ifs instead of else

This makes code more readable and maintainable, and if the common case is the valid one, then performance is better as we do not need to check for all possible invalid scenarios, which in most cases is much larger than the set of valid ones.

This leads into the other strategy I’ll mention: speed. The speed strategy consists of handling the common case first (or fastest case first if its performance is such that it warrants being checked first despite occurring less often). Typically this strategy requires profiling to observe which ordering yields the best performance. Another important aspect is to effectively use the else block. The fact that the code has progressed to the else block means that all of the other expressions evaluated to false. By taking advantage of this fact, you can shift slowly evaluating expressions into the else clause, thus avoiding needing to evaluate them. In general, if you can use an else instead of an else-if based on the pre-conditions resulting from all the expressions evaluating to false, then do so. Use an assert statement instead if you want to ensure the conditions are indeed met.

Altogether, remember that macro-optimization is much more crucial, but by writing better conditionals you can really squeeze out those small performance gains, especially when tight looping is involved. Most importantly, better conditionals greatly improve code readability and maintainability.