The process of optimizing the performance of a system, often with the help of a profiler tool, and often at the cost of complexity and developer sanity.
A quick introduction to some of the terms:
Things to keep in mind
- There is an art to all of this, so merely gaining the possession of a fancy profiler tool doesn’t mean you are almost done.
- Performance costs, so Customers should make the choice around acceptable performance. Is a 50% reduction in an operation’s time move valuable than a new feature?
- Code optimization often increase the complexity of the code and has a reasonable chance of introducing bugs. Be careful.
- Know when to stop. Set a target duration for a scenario.
- Profiler tools will effect the execution of your application, so don’t blindly accept everything it tells you as the truth.
- Budget your efforts by the amount of improvement it will give you. Speeding up a part of the system that only consumes 1% of the execution time is probably a waste of effort.
- If human perception is the arbitrator of good enough performance, then you might have a lot of cheaper tricks at hand. Say a progress bar.
- Try to code up a scenario as a test case for easier and more stable execution.
- Throw away code changes that don’t show sufficient performance improvements.
- Feel free to use; System.out.println(“** “+System.currentTimeMillis());
- Average out your results
- Watch out for the first run of a scenario, class loading, JIT compilication, data caching can radically change it from subsequent runs
Locating code to optimize based on hunches will lead to a lot of wasted effort on larger projects.
- Large amounts of time spent within a method.
- Methods called many times that produce the same result each time – Cache the result.
- Large number of objects allocated of a specific type
Once you have found a potential section of the system to optimize, which code transformations would be relevant?
Do Less. Work Concurrently. Move Calculations. Cheat.
- Caching the result of a calculation can save a lot of time. If the total result is too difficult, sometimes you can cache results for parts of the calculation, only needing the subset of the full calculation on demand.
- Downsides: It can also significantly complicate the code if the calculation could produce different results over time. Watch out for mutable state.
- Fast path code. If a certain case occurs lots of the time investigate if it can be handled by some special case code in a faster way.
- Downsides: More code, and branches complicate reading of the code later
- Concurrent execution. Machines now tend to have many cores and so support concurrent execution of code. In Java this probably means threads.
- Downsides: Where to begin? Deadlocks, object aliasing, complexity, and so on.
- Asynchronous Execution. If you don’t need the result of a calculation immediately, let in run in the background and come back later. This can often be used at the UI level. In java see FutureTask class for one approach.
- Downsides: This is constrained version of concurrent execution.
- Reduce IO. CPU’s are insanely fast, but disks and network aren’t. Hold back on flushes of OutputStreams. Order accesses of storage to minimize seek times.
- Downsides: Probably more code, holding onto data longer could increase memory demands.
- Reduce Correctness. How precise does a result need to be? Often heuristics will generate an approximate result significantly cheaper than a fully correct result. Perhaps an approximate answer now is good enough until the complete answer is available, as an example interlacedrendering of PNG images.
- Downsides: Complexity, changing result could confuse clients of the calculation.