Premature optimisation is an extremely common anti-pattern, and one that’s very difficult to resist. This is probably because of a wide variety of factors, including:
- Optimisation is fun! It’s problem-solving at its finest, with the potential for lots of clever tricks
- Optimisation can be comparatively easy and short-term: it’s easy to focus on just optimising the piece of code at hand, and so it’s a limited problem. In addition, there are reams and reams of resources out there that help you learn how to optimise code
- Once you’ve had to deal with a performance problem that really burnt you, it’s hard to put optimisation out of your mind!
Because of these and other reasons, any half-decent programmer will have a constant itch to optimise things that are before him. However, most of the time, these optimisations produce overall negative results. Generally, the two main reasons for this are that optimisations reduce code clarity and that they simply don’t work.
Reducing code clarity
The most damaging side-effect of premature optimisation is a reduction of the clarity of the code. In my opinion, there are two main kinds of code performance optimisations available: high-level optimisations and low-level optimisations.
High-level optimisations generally revolve around rethinking the purpose of your code and whether it actually needs to do “the slow thing” at all. A good example would be turning 5 separate ORM queries that fetch a different object each time, into a single sql query that does all 5 joins in one go and so returns the end result in a single query. Adding a lookup table to an intensive loop is also something that I’d consider to be a high-level optimisation trick (you’re removing the need to perform a calculation over and over during the loop).
Low-level optimisations are those that don’t change the logic of the code, but only the implementation of it. For instance, rewriting a method in C or assembler to speed it up, unbundling a loop, or adding an index to a database table. Picking a language for development concerns is also a low-level optimisation. For instance, anyone who decides to write their code in C instead of Java “because it’s faster” is making that sort of early optimisation.
Both kinds (high- and low-level) tend to result in greater obfuscation of the code. That’s not always the case of course (adding an index obfuscates nothing, for instance), but it’s generally true. All of the examples above other than the index are optimisation tricks that make the code more complicated, and even more fragile.
This obfuscation becomes a problem when it’s out of control. If you’ve decided to optimise your code, you carefully identify the bottlenecks, and you build optimisations for just those bottlenecks, clearly identifying them from the “rest of the code” so that any future developer knows that they are basically hacks put in place for a good reason, there’s no problem. If, however, you have a collection of programmers making all sorts of optimisations left right and centre, as part of their coding style, you’ll greatly increase the rate at which your code base turns into a giant ball of spaghetti.
And the worst part is, of course, all those optimisations will amount to nothing.
Most optimisations don’t work
The worst thing about premature optimisations is that they simply don’t work. Here’s why. There is only one process that works for optimising an application. It goes like this:
- Identify that there is a performance issue
- Locate the cause of the performance issue and confirm that location through profiling and/or benchmarks
- Apply patch to the cause of the performance issue
- Measure that the performance fix produces improvements in the benchmarks
If you don’t follow these simple four steps, your optimisations are worthless. This is because performance is not a pervasive quality that lives in your code. You don’t “write high-performance code” in general and therefore get a high performance application. Most people see applications as a complex arrangement of gears which will turn faster if you put enough oil on it to lubricate the spinning. That’s an incorrect image.
From a performance perspective, an application is best represented as a series of bottlenecks. Improving performance is merely the process of removing the worst bottlenecks, in order of severity. If the code base is clean and well architected, this is usually fairly straightforward. There is a lot of literature out there about how to fix specific performance bottlenecks. Some of the bottlenecks may be tricky, but I’ve yet to find one where you couldn’t apply a fairly straightforward high- or low-level optimisation and remove the bottleneck.
Can you remove these bottlenecks early? Most of the time, no! Sure, there are a few instances where you might spot a very obvious “bad performer” and be able to tell, from experience, that this will be a problem later. But even in those cases, you should hesitate to optimise before you’ve put up some benchmarks. Even if you think you recognise a potential bottleneck, there is no guarantee that this is an actual bottleneck for the application. Since the optimisation is not free (it takes time, and it reduces readability of the code), you should never optimise before you identify something worth optimising (by finding a performance problem).
Caveat: architecting for performance
There are some situations where concerning yourself with performance early is valid. Generally, that time comes before you write a single line of code, when you’re still exploring possible languages and architectures for the application.
This is most often the case in two situations: low-level frameworks, or excessively slow dependencies.
If you’re writing a piece of low-level framework (like a 3D engine or a search engine - almost anything with the word “engine” in it in fact), you’ll have to make some high-level architectural decision to ensure that the required performance is possible. These should not be attempted by developers who do not already have a very good feel for what the performance bottlenecks are likely to be in this specific problem domain, however. If you’re thinking of writing a regular expression engine and you have no idea of what the bottlenecks are in the domain of string processing, give up and use/study someone else’s library until you do.
If you’re dealing with excessively slow dependencies (e.g. facebook queries, which can take half a second to return data), you must give some thought to how you will isolate yourself from these dependencies from an architectural point of view. For instance, within the realm of facebook apps, you need to ensure that your application never needs to make more than one or two queries to facebook to generate a page view.
If you’re building a web application of some sort, like 90% of all developers out there, don’t worry about performance until you must.
If you do hit a performance issue, measure it, fix it, and then measure the improvement.
If you’re building a low-level engine, consider performance in designing your architecture.
If you’re dealing with a very slow dependency, think about isolating your application from it through a well designed architecture.
Stop Worrying About Performance!
Use the following link to trackback from your own site: