Boolean expressions evaluation and performance

In one great book that I was reading - The Art of Computer Systems Performance Analysis: Techniques for Experimental Design by Raj Jain I found a list of techniques for improving program performance and there was one point:

Arrange a series of AND conditions so that the condition most likely to fail is tested first

Hmm, but from the boolean algebra, a branch of the math behind boolean operators in software programming we know that for the logical conjunction (the fancy way mathematicians use to name AND operator) the law of commutativity applies:

x AND y = y AND x

Benchmarks

I use Go programming language to illustrate this performance technique.

import (
	"math/rand"
	"time"
)

func isBlocked() bool {
	time.Sleep(20*time.Millisecond)
	return rand.Float32() < 0.05
}

func isConfirmed() bool {
	time.Sleep(20*time.Millisecond)
	return rand.Float32() < 0.5
}

func IsActive() bool {
	return !isBlocked() && isConfirmed()
}

Here we have a function IsActive() that checks if something is active, let’s say a user. By the business rules, a user is active if it’s not blocked and is confirmed - nice and clean boolean expression. We have two routines: isBlocked and isConfirmed. I added time.Sleep to each of them to emulate some significant amount of work: it could be an IO request to a database, for instance.

Also, these two functions return true value with some probability. We assume two things here: - Around half of users are confirmed - Around 5% of users are blocked

And here is the benchmark:

import "testing"

func BenchmarkIsActive(b *testing.B) {
	for i := 0; i < b.N; i++ {
		IsActive()
	}
}

and the results:

$ go test -bench=BenchmarkIsActive
goos: darwin
goarch: amd64
pkg: benchmarks
cpu: Intel(R) Core(TM) i7-8569U CPU @ 2.80GHz
BenchmarkIsActive-8          28   41255579 ns/op
PASS
ok   benchmarks 1.413s

slightly more than 40ms per operation. As I said before only 5% of users are blocked, and it means that with 95% probability !isBlocked() returns true. And there’s only a 50% chance to evaluate isConfirmed() to true, so it does make sense to swap conditions as in the piece of advice I quoted at the beginning of this post ("Arrange a series of AND conditions so that the condition most likely to fail is tested first").

Let’s do it and bechmark again:

func IsActive() bool {
	return isConfirmed() && !isBlocked()
}
$ go test -bench=BenchmarkIsActive
goos: darwin
goarch: amd64
pkg: benchmarks
cpu: Intel(R) Core(TM) i7-8569U CPU @ 2.80GHz
BenchmarkIsActive-8          56   29593772 ns/op
PASS
ok   benchmarks 1.893s

It’s ~30ms per operation, much faster! The reason is that compiler or interpreters of probably every modern programming language (yes, even JavaScript) has a simple optimisation for binary logical operators - the right operand of does not get evaluated if left one’s value is enough to get the result. In the case of AND if means that if we have the expression x AND y and x == false it does not matter what’s the value of y - the result will be false anyway, so there is no point in evaluating it.

It wasn’t mentioned in the bool, but obviously same applies to other binary boolean operators, for example OR: arrange a series of OR conditions so that the condition most likely to be true is tested first. Taking the same example:

func IsInactive() bool {
	return !isConfirmed() || isBlocked()
}

Conclusion

The order of boolean operators does make difference. However, in most cases, performance is not an issue and you should order your expressions in the order that is better for readability.