Index Previous Next

Pushing duplicate constants to stack

Consider the following code:

   public class test {
       public void f(int i, int j) {
       }

       public void g() {
           f(6, 6) ;
       }
   }

When this is compiled and disassembled we find that the call to f() comes out as:

   aload_0
   bipush 6
   bipush 6
   invokevirtual test/f(II)V

bipush 6 is a two byte instruction, consisting of the hex opcode 0x10 followed by the constant byte value 6. The 8-bit constant is sign extended to a 32-bit int and pushed onto the stack. Replacing the second bipush with a dup will have the same effect, but saves one byte.

   aload_0
   bipush 6
   dup
   invokevirtual test/f(II)V

The bipush instruction can only be used for constant values which fit in a signed 8-bit integer (-128 to 127). Larger constants require the use of either the sipush, ldc or ldc_w instructions. sipush is a three byte instruction: opcode followed by a 16-bit signed integer constant. For larger values still ldc and ldc_w must be used. These take one or two byte indices into the constant pool of the current class to identify the quantity to be pushed onto the stack. The item in the constant pool can be an int, a float or a String.

When we find two identical sipush, ldc or ldc_w instructions next to one another we can always replace the second by a dup to save one byte in the class file.

The same technique can be applied to the ldc2_w instruction, which pushes a two-word constant (long int or double) onto the stack. In this case, though, we must use the dup2 instruction instead of dup, to duplicate the two-word value on the stack.

In the initial example above I deliberately used the constant value 6 rather than a smaller integer. There are special one-byte instructions to push the values -1, 0, 1, 2, 3, 4 and 5. Replacing one of these with a dup won't give any saving.

I have measured the time taken to perform a million calls to the method g() for different values of the arguments, then subtracted the time to perform the same number of calls to a method taking no arguments. The measurements were made using JDK 1.0.2 on a 133 MHz Pentium running Linux 2.0.33. Here are the results:

       Constant    Bytes    Unoptimised       Optimised
                   saved     time (ms)        time (ms)

             1       0          311              362 
             6       1          407              409 
           146       2          486              446 
       100,000       1          431              414 

Clearly there is no advantage in replacing the special instructions for small integer constants: the class file is no smaller and the modified code runs more slowly. In all other cases there is some advantage to be gained from the optimisation. The improvement is particularly marked for two-byte constants (e.g. 146).


Index Previous Next