Performance

0
305
Blue Ferrari car at the race track.

Importance Of Performance

For some applications performance is extremely important. E.g. in cars automatic breaking systems need to process all relevant information quickly to initiate an emergency breaking in time. Another example are high-frequency trading systems. They need process information very fast in order to maximize profits.

For most applications performance is not critical. Still it is important. E.g. excessive processing time can result in higher costs.

Also, did you ever have to work with a slow application? An application where every action takes several seconds to complete? This is a tiresome experience. It’s hard to stay focused when the natural flow of thoughts is constantly interrupted with forced waiting time. Furthermore, a slow application just does not scream quality.

Performance Is A Trade-Off

While performance is important, it’s by no means the only relevant quality of an application. Good source code is also developed cost efficiently, runs correctly, is easy to read, easy to change and extend, runs stable, is cheap to maintain, and so many other things.

Source code that is written with performance in mind first and foremost – almost by definition – is harder to read, harder to change, harder to extend, harder to get correct, more expensive to write, etc.

A Simple Example

Let’s take a quick look at a real life example that I’ve seen several times throughout the years.

// Standard
logger.debug("Loading application configuration from '" + config.getConfigFilePath() + "'...");
loadConfig(config.getConfigFilePath());

// Optimized
If (logger.isDebugEnabled()) {
	StringBuilder sb = new StringBuilder();
	sb.append("Loading application configuration from '");
	sb.append(config.getConfigFilePath());
	sb.append("'...");
	logger.debug(sb.toString());
}
loadConfig(config.getConfigFilePath());

The second version contains two optimizations. Firstly, it only creates the logging message, if log level debug1 is enabled. Hence we expect it to be faster if a lower log level is configured. Secondly, it uses a StringBuilder instead of using String concatenation.

Analysis

Is the second version better? It takes up more lines on the screen and hence more vertical screen space. Vertical screen space is one of the more important factors with regards to to readability. Humans are pretty good at comprehending things as long as they fit on the screen. As soon as they have to scroll however, they tend to loose context.

In addition the second version introduces duplication: it mentions the log level (debug) twice in different ways.

So clearly the second version is a trade-off, it sacrifices some readability and introduces some duplication in order to achieve higher performance.

But is it actually faster? This is hard to say. In Java the source code is not just executed as is, but is first compiled to an intermediate language and then interpreted. The interpretation and execution is done on modern hardware. All of these layers2 apply sophisticated optimizations. It is very possible that the compiler generates the exact same intermediate representation for both implementations. Furthermore it’s possible that the optimized code is actually slower. After all it does an additional check for the log level, and it creates an additional object (StringBuilder).

The most important question though is: does it matter? Both versions are fast. Very, very fast. No one will ever notice a difference between those two implementations. Plus the code is executed only once during application start up – so no end user will ever observe it anyway.

In this example readability has been sacrificed and duplication has been introduced without gaining any tangible value.

More computing sins are committed in the name of efficiency (without necessarily achieving it) than for any other single reason – including blind stupidity.

W. A. Wulf

Don’t Optimize Your Code

Most code is executed very fast on modern systems. So whatever smart optimization you might think of while writing the code will not make any noticeable3 difference. However, it will make your code more difficult to read, maintain, and test. So optimizing code as you write, will most probably have no impact on performance, but will degrade the quality of the code. Therefore, I strongly recommend not optimizing code when writing it.

Identify Bottle Neck And Implement Minimal Fix

Every now and then we do run into performance issues.4 In these cases you don’t want to just optimize every single line in your application.5 You want to focus on the part of your code that actually causes the problem.

So step one is to identify the bottle neck. There are two easy ways to do so. You can either add a couple of debug statements to your code that indicate where the application is in the control flow at which time stamp. Or you can use a profiler. Either way you will identify the problematic part quickly.

Now step two is to optimize just that part of the application. Not optimizing the code in the first place was a good idea. We have written the code with readability in mind, so the code now is easy to read, change and optimize.

Conclusion

  • Don’t optimize yet. Focus on code quality.
  • Test.
  • Identify the relevant part and implement minimal fix.

Jackson’s Rules of Optimization: Rule 1. Don’t do it. Rule 2 (for experts only). Don’t do it yet – that is, not until you have a perfectly clear and unoptimized solution.

Michael A. Jackson
Highway lights night.

Additional Thoughts

Here is some additional food for thought with regards to performance.

Sometimes performance issues can be solved by simply assigning more resources6. If this is applicable to your problem at hand, it’s usually a good idea. These days hardware is cheap, while engineering time is expensive.

We all got thought about asymptotic runtime at university – and it is a valuable concept. However, asymptotic runtime only dominates the runtime when the input size is very large. E.g. if you are sorting a list of 5 elements, there really is no reason to use quick sort over bubble sort. In these cases it’s best to forget about asymptotic runtimes and just use the simplest algorithm that solves the problem.7

Notable Exceptions

I stick to the recommendations outlined above religiously. With the following few exceptions:

  • I put some thought into my database design. E.g. I make sure all tables have an integer data id to access records quickly. I create indexes on fields that I use to access records.
  • I put some thought into Web Service calls. E.g. if I require an object, that I get through a Web service call, twice within a method, I make sure I hold the object in RAM, instead of pulling it from the Web service a second time.
  • I try to avoid loading a large amount of data into memory. Often there is a more efficient way to solve the same task.8
  • In the rare cases where I have to process a large amount of data in memory, I make sure I use an algorithm with a good asymptotic runtime.
Ferrari 458 Spider
  1. or higher
  2. and potentially additional layers
  3. or even measureable
  4. Ideally you want to do meaningful load and performance testing before going to production with your code.
  5. Remember, most of it runs very fast anyway.
  6. E.g. CPU Power, RAM, pods, etc.
  7. Following the KISS principle
  8. E.g. applying changes on the database layer or using streaming.