[caption id="attachment_173" align="alignleft" width="300" caption="Photo by Ethan Hein"][/caption]
In this post, we're going to introduce the concept of code optimisation. We'll present the big picture of what it is, why it's important, and cover the main things you need to consider when thinking of optimising your code.
Optimisation is the art of making your code run more quickly and/or using fewer of the available computing resources. Sometimes this is desirable - completing an analysis in an hour rather than a week means you can get more done. Sometimes it’s a necessity - your computer only has 500MB of RAM, so your software had better not need any more. Ever. But sometimes it’s actually not important - if your software runs in a tenth of a millisecond, but need to wait for interactive user input, you’re already as quick as you need to be! This is the key question you must ask yourself when thinking about optimisation: Do I need this piece of code to go faster or use less memory?
If you do need to optimise…
Sometimes it can be important, even vital, to optimise. A scientific analysis that takes a week might be viable; there are very few analyses that run for more than a year that are worth the effort (the Human Genome project example is one, but we bet you can’t name many more). There are a number of different approaches that you can take to optimise your software. Often it's a blend of all of them that will give you the best results, but you’ll need to use your judgement in each case as good solutions will be code-, project- and deadline-specific, as well as relying on your skill as a developer.
Get a better computer
This probably doesn’t count strictly as optimisation, but the easiest way to make your software run quicker might be to run it on a faster computer. This might involve buying a higher-spec machine, or it might just be getting access to a faster existing machine. Many science departments have access to multi-CPU machines, clusters and even supercomputers. Ask around. Maybe someone knows something useful about resources you can gain access to! This can be optimisation for the price of a 30-second conversation.
This is what people normally mean by optimisation – making changes to the code that improve the software’s performance in some way. There are three important general types of software optimisation that it's useful to know about, algorithmic improvements, code improvements, and memory-versus-speed trade-offs.
An algorithmic improvement is when you change the algorithm that your code is using. These are by far the most powerful code optimisations you can make, and can lead (sometimes) to orders of magnitude improvement in performance, if well thought out. An algorithmic improvement is finding a fundamentally better way of doing something. Such optimisations are always critically dependant on what exactly you’re doing, and often rely on deciding if what approximations you can make, given what your objectives are.
Code improvements are where you don’t change the underlying algorithm, but you do change it’s implementation in the code. A defining characteristic of code optimisation is that, for a given input, your software should produce the same output before and after the improvement. It will just do so more quickly and/or using less memory. The experience of many developers over many years suggests that you’re unlikely to get more than a factor of 2-10 improvement (at most) in the performance of your code using this kind of optimisation. This is very important to consider; if your code runs in a week, but you need it to run in three days, then you might be okay with code optimisation. If you need it to run in a minute, you need to use a quicker algorithm in the first place.
Finally, memory-versus-speed trade-offs are a case that often occurs when you can change your code so that it's faster, but uses more memory (or vice versa). This is because of how computers handle data. Any data to be used by a given piece of software must be read in to RAM from a hard drive, across a network or something like that. This reading-in takes a bit of time. To deal with a set of data, a process can either read them all in at once, or in chunks, operating on each chunk in turn. Reading them all at once is faster, but requires enough RAM to hold all the data; reading in chunks only requires enough memory to hold a single chunk, but the periodic data-reading will slow the process down.
Bottlenecks and profiling
The key to any optimisation is to know where the bottlenecks are in your code. A code bottleneck is the part of the code that limits how quickly things can proceed. Our goal is to find these bottlenecks and then to fix them. There’s no point of optimising sections of the code that aren’t bottlenecks – by definition, you won’t get much improvement in performance. The key method in spotting bottlenecks is profiling. This can be done using a profiler (a tool that tracks how much CPU time different parts of your code take), or sometimes even just be inserting print statements in your code (getting it to print to screen the time after every stage is a quick and easy way to do this). Once you’ve used one or more of these to identify which parts of the code the computer spends most of its time on, you can go and have a look at that code and try to spot what it’s doing that’s taking so long.
This is as bad as it sounds. Optimisation is fine-tuning of your code and as such is very specific to the exact nature of your code. Imagine tweaking a car engine for optimum power output, but then stripping out the fuel injection system, adding a couple more cylinders and deciding to run it on liquid hydrogen – all your careful fine-tuning would become pointless. So it is with computer code. It's also true that optimisation will generally make your code less flexible and hence more effort to change. This is a Bad Thing and should be avoided as much as possible.
You can’t keep optimising forever (well, you can, but you shouldn’t). There will come a point where you’ve made any big/easy gains in performance that are possible and you’ll increasingly be faced by potential improvements that are smaller and/or take longer to implement. This is a law of diminishing returns. This means you have to make a judgement as to when it is no longer worth continuing to optimise your code. This will be a context-specific judgement, depending on the exact nature of the project you’re undertaking. For example, if another week of coding can gain you a factor of two speed improvement that reduces run time from two months to one month, it might well be worth doing (provided you didn’t need that week for anything more important). Conversely, if that week’s effort only gains you a 10% reduction in memory usage, but you’re already operating comfortably inside your machine’s RAM limit, don’t bother – you should spend that week on something more productive.
Optimisation can make your code run more quickly and/or using less memory. If you need it to. Be aware that there are a range of techniques that will allow you to invest some of your precious time in order to improve your code in this way. Decide on whether this is necessary/desirable and, if it is, consider the types of optimisation that might give you the performance boost you need.