Switches Loops and Switches

- 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