- 156 -
array = null; System.gc ;
} public static void no_exception3
{ Create the Vector, get the time, and run the standard loop.
java.util.Vector vector = new java.util.VectorSIZE; vector.setSizeSIZE;
java.util.Enumeration enum = vector.elements ; Object nothing;
long time = System.currentTimeMillis ; for ; enum.hasMoreElements ;
nothing = enum.nextElement ; System.out.printlnEnumeration loop with no exceptions took +
System.currentTimeMillis -time + milliseconds; Garbage collect so that we dont run out of memory for
the next test. We need to set the variables to null to allow the instances to be garbage collectable.
enum = null; vector = null;
System.gc ; }
public static void with_exception3 {
Create the Vector, get the time, and run a non-standard loop with no termination test using the
java.util.NoSuchElementException to terminate the loop. java.util.Vector vector = new java.util.VectorSIZE;
vector.setSizeSIZE; java.util.Enumeration enum = vector.elements ;
Object nothing; long time = System.currentTimeMillis ;
try {
for ; ; nothing = enum.nextElement ;
} catch java.util.NoSuchElementException e {}
System.out.printlnEnumeration loop with an exception took + System.currentTimeMillis -time + milliseconds;
Garbage collect so that we dont run out of memory for the next test. We need to set the variables to null to
allow the instances to be garbage collectable. enum = null;
vector = null; System.gc ;
} }
7.3 Switches
The Java bytecode specification allows a
switch
statement to be compiled into one of two different bytecodes. One compiled
switch
type works as follows: Given a particular value passed to the
switch
block to be compared, the passed value is successively compared against the value associated with each case statement in order. If, after
testing all cases, no statements match, then the default label is matched. When a
case
statement that
- 157 - matches is found, the body of that statement and all subsequent
case
bodies are executed until one body exits the
switch
statement, or the last one is reached. The operation of this
switch
statement is equivalent to holding an ordered collection of values that are compared to the passed value, one after the other in order, until a match is determined. This
means that the time taken for the
switch
to find the case that matches depends on how many
case
statements there are and where in the list the matched case is. If no cases match, and the
default
must be used, that always takes the longest matching time. The other
switch
bytecode works for
switch
statements where the case values all lie in a particular range or can be made to lie in a particular range. It works as follows:
Given a particular value passed to the
switch
block to be compared, the passed value is tested to see if it lies in the range. If it does not, the
default
label is matched; otherwise, the offset of the case is calculated and the corresponding case is matched directly. The body of that matched label
and all subsequent
case
bodies are executed until one body exits the
switch
statement, or the last one is reached.
For this latter
switch
bytecode, the time taken for the
switch
statement to match the
case
is constant. The time is not dependent on the number of
case
s in the
switch
, and if no
case
s match, the time to carry out the matching and go to the
default
is still the same. This
switch
statement operates as an ordered collection with
switch
value first being checked to see if it is a valid index into the ordered collection, and then that value is used as the index to arrive immediately at the
matched location.
Clearly, the second type of
switch
statement is faster than the first. Sometimes compliers can add dummy
case
s to a
switch
statement, converting the first type of
switch
into the second faster kind. A compiler is not obliged to use the second type of
switch
bytecode at all, but generally it does if it can easily be used. You can determine which
switch
a particular statement has been compiled into using
javap
, the disassembler available with the JDK. Using the
-c
option so that the code is disassembled, examine the method that contains the
switch
statement. It contains either a tableswitch bytecode identifier, or a lookupswitch bytecode identifier. The
tableswitch
keyword is the identifier for the faster second type of
switch
. If you identify a bottleneck that involves a
switch
statement, do not leave the decision to the compiler. You are better off constructing
switch
statements that use contiguous ranges of
case
values, ideally by inserting dummy
case
statements to specify all the values in the range, or possibly by breaking up the
switch
into multiple
switch
es that each use contiguous ranges. You may need to apply both of these optimizations as in the next example.
Our
tuning.loop.SwitchTest
class provides a repeated test on three methods with
switch
statements, and one other array-access method for comparison. The first method,
switch1
, contains some noncontiguous values for the
case
s, with each returning a particular integer value. The second method,
switch2
, converts the single
switch
statement in
switch1
into four
switch
statements, with some of those four
switch
statements containing extra dummy
case
s to make each
switch
statement contain a contiguous set of
case
s. This second method,
switch2
, is functionally identical to
switch1
. The third method,
switch3
, replaces the
case
s with a contiguous set of
case
s, integers 1 to 13. This method is not directly comparable to the first two methods; it is present as a control test. The
fourth method,
switch4
, is functionally identical to
switch3
, but uses an array access
- 158 - instead of the
switch
statement, essentially doing in Java code what the compiler implicitly does in bytecodes for
switch3
. I run two sets of tests. The first set passes in a different integer for each call to the
switch
es
.
This means that most of the time, the
default
label is matched. The second set of tests always passes in the integer 8 to the
switch
es. The results are shown in Table 7-2
for various VMs. Varying and constant refer to the value passed to the
switch
statement. Tests labeled varying passed different integers for each iteration of the test loop; tests labeled constant
passed the integer 8 for each iteration of the loop.
Table 7-2, Speedup Using Exception-Driven Loop Termination
1.2 1.3