6.4 Variables

Local (temporary) variables and method-argument variables are the fastest variables to access and update. Local variables remain on the stack, so they can be manipulated directly; the manipulation of local variables depends on both the VM and the underlying machine implementation. Heap variables (static and instance variables) are manipulated in heap memory through the Java VM-assigned bytecodes that apply to these variables. There are special bytecodes for accessing the first four local variables and parameters on a method stack. Arguments are counted first; then, if there are fewer than four passed arguments, local variables are counted. For nonstatic methods, this always takes the first slot. longs and doubles each take two slots. Theoretically, this means that methods with no more than three parameters and local variables combined (four for static methods) should be slightly faster than equivalent methods with a larger number of parameters and local variables. It also means that any variables allocated the special bytecodes should be slightly faster to manipulate. In practice, I have found any effect to be small or negligible, and it is not worth the effort involved to limit the number of arguments and variables.

Instance and static variables can be up to an order of magnitude slower to operate on when compared to method arguments and local variables. You can see this clearly with a simple test comparing local and static loop counters:

package tuning.exception;
  
public class VariableTest2
{
  static int cntr;
  public static void main(String[  ] args)
  {
    int REPEAT = 500000000;
  
    int tot = 0;
    long time = System.currentTimeMillis( );
    for (int i = -REPEAT; i < 0; i++)
      tot += i;
    time = System.currentTimeMillis( ) - time;
    System.out.println("Loop local took " + time);
  
    tot = 0;
    time = System.currentTimeMillis( );
    for (cntr = -REPEAT; cntr < 0; cntr++)
      tot += cntr;
    time = System.currentTimeMillis( ) - time;
    System.out.println("Loop static took " + time);
  
  }
}

Running this test results in the second loop taking significantly longer than the first loop (see Chapter 6).

Table 6-6. The cost of nonlocal loop variables relative to local variables

Times relative to local loop variables

1.1.8

1.2.2

1.3.1

1.3.1-server

1.4.0

1.4.0-server[7]

1.4.0-xInt

Static variable time/ local variable time

122%

126%

296%

259%

226%

N/A

127%

Static array element/ local variable time

126%

127%

630%

1034%

315%

N/A

211%

[7] The 1.4 JVM JIT compiler in server mode identified that the test was effectively a repeated constant expression and collapsed the loop to one call, thus eliminating the test. Other tests have shown that the costs of static and array elements compared to local variables are still present in 1.4.0 server mode, but this test cannot show those costs.

If you are making many manipulations on an instance or static variable, it is better to execute them on a temporary variable, then reassign to the instance variable at the end. This is true for instance variables that hold arrays as well. Arrays also have an overhead, due to the range checking Java provides. So if you are manipulating an element of an array many times, again you should probably assign it to a temporary variable for the duration. For example, the following code fragment repeatedly accesses and updates the same array element:

for(int i = 0; i < Repeat; i++)
  countArr[0]+=i;

You should replace such repeated array element manipulation with a temporary variable:

int count = countArr[0];
for(int i = 0; i < Repeat; i++)
  count+=i;
countArr[0]=count;

This kind of substitution can also apply to an array object:

static int[  ] Static_array = {1,2,3,4,5,6,7,8,9};
  
public static int manipulate_static_array( ) {
  //assign the static variable to a local variable, and use that local
  int[  ] arr = Static_array;
  ...
  
//or even
public static int manipulate_static_array( ) {
  //pass the static variable to another method that manipulates it
  return manipulate_static_array(Static_array);}
public static int manipulate_static_array(int[  ] arr) {
 ...

Array-element access is typically two to three times as expensive as accessing nonarray elements.[8] This expense is probably due to the range checking and null pointer checking (for the array itself) done by the VM. The VM JIT compiler manages to eliminate almost all the overhead in the case of large arrays. But in spite of this, you can assume that array-element access is going to be slower than plain-variable access in almost every Java environment (this also applies to array-element updates). See Section 4.5 in Chapter 4 for techniques to improve performance when initializing arrays.

[8] Mark Ruolo, "Accelerate Your Java Apps," JavaWorld, September 1998, http://www.javaworld.com/javaworld/jw-09-1998/jw-09-speed.html.

ints are normally the fastest variable type to operate on. long s and doubles can take longer to access and update than other variables because they are twice the basic storage length for Java (which is four bytes). The Java specification allows longs and doubles to be stored in more than one action. The specification allows the actual manipulation of longs and doubles to be implementation- and processor-dependent, so you cannot assume that longs and doubles always take longer. If you have one specific target environment, you can test it to determine its implementation. Note that because of the specification, longs and doubles are the only data types that can be corrupted by simultaneous assignment from multiple threads (see Section 10.6 in Chapter 10 for more details).

When executing arithmetic with the primitive data types, ints are undoubtedly the most efficient. shorts, bytes, and chars are all widened to ints for almost any type of arithmetic operation. They then require a cast back if you want to end up with the data type you started with. For example, adding two bytes produces an int and requires a cast to get back a byte. longs are usually less efficient. Floating-point arithmetic seems to be the worst.

Note that temporary variables of primitive data types (i.e., not objects) can be allocated on the stack, which is usually implemented using a faster memory cache local to the CPU. Temporary objects, however, must be created from the heap (the object reference itself is allocated on the stack, but the object must be in the heap). This means that operations on any object are invariably slower than on any of the primitive data types for temporary variables. Also, as soon as variables are discarded at the end of a method call, the memory from the stack can immediately be reused for other temporaries. But any temporary objects remain in the heap until garbage collection reallocates the space. The result is that temporary variables using primitive (nonobject) data types are better for performance.

One other way to speed up applications is to access public instance variables rather than use accessor methods (getters and setters). Of course, this breaks encapsulation, so it is bad design in most cases. The JDK uses this technique in a number of places (e.g., Dimension and GridBagConstraints in java.awt have public instance variables; in the case of Dimension, this is almost certainly for performance reasons). Generally, you can use this technique without too much worry if you are passing an object that encapsulates a bunch of parameters (such as GridBagConstraints); in fact, this makes for an extensible design. If you really want to ensure that the object remains unaltered when passed, you can set the instance variables to be final (as long as it is one of your application-defined classes).