Performance Tips: Java without a JITLoopsOptimising the comparisons involved in a loopIn the previous section on variable access, we touched on one method of loop optimisation. Namely, it is important to move relatively expensive operations to outside the loop where possible. There are two key points to remember when optimising the actual boilerplate code around the loop. Firstly, comparison with zero is a little more efficient than comparison with an arbitrary number. And secondly, it is important to avoid calculating or fetching values every time through the loop even when it looks like we're not. Take the typical case of looping through an array. A common code pattern is: public void loop() { int[] array = ... for (int i = 0; i < array.length; i++) { // ... do something } } It's not necessarily obvious from the way the code is laid out, but the VM will have to execute bytecodes
to fetch the array's length-- or whatever you put in the place of public void loop() { int[] array = ... for (int len = array.length, i = 0; i < len; i++) { // ... do something } } A second optimisation is possible if it does not matter if you loop through the array backwards. (for example, if the purpose of the loop is to sum the elements of the array): public void loop() { int[] array = ... for (int i = array.length-1, i >= 0; i--) { // ... do something } } The reason that this can give a slight gain is that Java has special bytecodes for comparison with zero. These are slightly more efficient than comparing with an arbitrary value simply because it isn't necessary to fetch the 'arbitrary value' and put it on the stack in advance. Look out for these optimisations in the JDK source code. Breaking out of a loop with an exceptionThis "optimisation" is a bit contraversial because: (a) it involves clearly ugly code; (b) under many circumstances it doesn't turn out to be an optimisation; (c) it's usually mentioned without explaining the circumstances necessary for it to actually be an optimisation. Nonetheless, it's worth mentioning that under the right circumstances it can be more efficient to avoid a loop comparison altogether and to break out of the loop with an exception: public void loop() { int[] array = ... int i = 0, count = 0; try { while (true) { count += array[i++]; } } catch (ArrayIndexOutOfBoundsException ignore) {} } Whether this is genuinely an optimisation depends on: (a) how many times throgh the loop you're going; and (b) how much optimisation has gone into exception throwing in the JVM you're running in. The trick can work because you're avoiding doing the bounds checking in bytecode and instead using the "built-in" bounds checking that the VM must do on every array access. The trouble is that you're guaranteed to throw an exception at the end of the loop. Throwing an exception is generally an expensive operation because the VM must make some kind of copy of the current stack position and then figure out where to 'roll back' to. (This rolling back in turn involves examining a table or tables of bytecode offsets to determine which exception handler code to call; compared to the execution of your average bytecode, this is expensive.) And given that exceptions are really designed for exceptional cases, they may not have been high up on the priority list for optimisation by the designers of your VM. The bottom line is that this optimisation may be suitable if: (a) you don't mind the hideous code; (b) your platform has no JIT compiler; (c) you know that the code will loop round many times; (d) exception throwing performance isn't particularly hideous on your platform. All editorial content copyright (c) Neil Coffey 2007. All rights reserved. |