Optimizing Java Programs using Generic Types Eli Mayfield Kyle Roth Elena Machkasova Daniel Selifonov Nathan Dahlberg mayf0016, roth0323, elenam, selif004, dahlb061@morris.umn.edu University of Minnesota, Morris ABSTRACT Our research involves improving performance of programs written in the Java programming language. By selective specialization of generic types, we can enable the compiler to eliminate typecasting, and provide type information to remove dynamic method lookup at runtime. An example of this specialization using Quicksort improved performance by over 20%. Categories and Subject Descriptors D.3.3 Language Constructs and Features - Classes and objects, Inheritance, Polymorphism D.3.4 Processors - Optimization General Terms Performance, Languages 1. INTRODUCTION Generic types were introduced into the Java programming language in October 2004. Generic types ensure type safety, guaranteeing that all data used will match a type parameter. Java implements generic types by type erasure; after objects are checked to match the type parameter, type information is removed. Typecasts to specific instance types are then inserted by the compiler. Most Java compilers convert programs into platform-independent bytecode which is then ran by the Java virtual machine, or JVM. Just-In-Time compilers were added to the JVM to improve performance through dynamic compilation, converting bytecode into native code as a program runs. Sun Microsystems developed the HotSpot JVM to selectively compile only frequently called methods into native code, decreasing the overhead caused by JIT compilers. HotSpot has two modes, Client and Server. Client mode starts programs as quickly as possible, while Server mode focuses on optimizing the runtime of the program, performing more effective but time-consuming optimizations. As a result, long programs often (though not always) run faster in Server mode. Because our optimization is most beneficial to large computations, our results are listed in Server mode times. Client mode results differ from Server mode, but our goal is to improve Server mode while maintaining, and hopefully improving, Client mode performance. 2. OUR OPTIMIZATION We optimize programs by selectively creating copies of generic Permission to make digital or hard copies of all or part of this work for personal or classroom use is granted without fee provided that copies are not made or distributed for profit or commercial advantage and that copies bear this notice and the full citation on the first page. To copy otherwise, or republish, to post on servers or to redistribute to lists, requires prior specific permission and/or a fee. classes with more specific class bounds. A general-purpose container such as ArrayList would have a type parameter of <E>. We can specialize ArrayList by creating a separate copy for each type of data it will hold. For instance, if a program uses ArrayLists to store its data of Integers and Strings, our optimization would create specialized ArrayLists with bounds of <E extends Integer> and <E extends String>. This changes the names of the classes (for instance, from ArrayList to ArrayListInteger and ArrayListString), so references have to be changed in the rest of the program. This change can also be made to classes with parameters to begin with; for example, a Quicksort class might be parameterized to <T extends Comparable<T>>, which can be specialized in the same way. Specializing classes to have more specific bounds improves performance due to method lookup. With a general type, the Java compiler must find the closest method in the inheritance tree; if the type parameter of the class is specific enough, the Java compiler can use devirtualization to call a method directly with no runtime lookup at all. There are several limitations to our optimization. This is a wholeprogram optimization – all code must be known. We assume there is no dynamic code loading or reflection. Specializing the bound is only effective when the new bound is specific enough that it uniquely determines the method whose call we are trying to optimize. We cannot specialize any class that uses static data; this data is shared between instances of the class and creating specialized copies of the class would create multiple copies of the data. Additionally, classes with explicit type-erased operations cannot be optimized since changing the bounds changes the type used in the type erasure. 2.1 Partial Optimization Not all classes in a program need to be specialized in order to see results of specialization. A complete specialization would specialize every parameterized class used in a program, and all the parameterized classes they inherit from or extend, continuing all the way up their inheritance tree. We can also specialize a program without this much change by only specializing the classes directly used in the program, or even only one or two classes which will make the most difference Such optimizations are referred to as partial specialization. 3. RESULTS USING QUICKSORT As an example to find the effects of our optimization, we use an implementation of Quicksort. This implementation took in an ArrayList and sorted its data using the compareTo() method, choosing a random pivot point at each step. By sorting large lists of data many times, we were able to measure the improvement in performance caused by our optimization: to this end, we used two lists of 1,500 numbers, randomly generated with a constant seed; one list stored the numbers as Integers and one list stored the numbers as Strings. We used our own copies of the standard Java code for these classes, along with the Collections classes. Each test looped through this process 30 times, and we executed twenty tests per run. 3.1 Variations in Optimizations 3.1.1 Original We tested five variants of Quicksort. The Original (O) implementation of Quicksort used a Quicksort class parameterized to <T extends Comparable<T>>, and stored data in ArrayLists parameterized to <E>. 3.1.2 ArrayList The ArrayList (AL) implementation used an un-optimized Quicksort class, but stored data in specialized ArrayLists, an ArrayListInteger parameterized to <E extends Integer> and an ArrayListString parameterized to <E extends String>. 3.1.3 Quicksort The Quicksort (QS) implementation used specialized copies of Quicksort, a QuicksortInteger parameterized to <T extends Integer> and a QuicksortString parameterized to <T extends String>, but used un-optimized ArrayLists. 3.1.4 QuicksortArrayList The QuicksortArrayList (QSAL) implementation used the specialized Quicksort classes from QS and the specialized ArrayList classes from AL. 3.1.5 Complete Finally, the Complete (C) implementation used the specialized Quicksort and ArrayList classes, and also specialized all other classes used by ArrayList that could be specialized (Iterable, AbstractCollection, AbstractList, Collection, Iterator, and List). 11 seconds. This would suggest an improvement of about 27% for this particular example. 4. CONCLUSIONS The differences in performance can be attributed clearly to our optimization. Specializing the Quicksort class allows compareTo() to be called directly from Integer or String due to devirtualization, removing the time spent from method lookup at runtime. When the ArrayList class is specialized, the bytecode implementation of the program changes from retrieving elements changes from Objects: ArrayList s = new ArrayList(); String in = “hello”; s.insert(str); String out = (String) s.get(0); to a more succinct variant returning Strings, removing the need for a typecast: ArrayListStr s = new ArrayListStr(); String in = “hello”; s.insert(str); String out = s.get(0); Specializing type parameters is clearly beneficial to the performance of a program. It removes runtime method lookup, and in programs where this is a significant portion of the time spent overall (such as Quicksort), it makes a significant change. The downside of this optimization is that it causes significant code duplication – especially Complete specialization. However, this drawback is not as pronounced with partial optimizations, and some partial optimizations can perform just as well as (and occasionally better than) a complete specialization. We saw substantial speed improvements to modifying a single class in the Quicksort implementation, which is very promising. 5. FUTURE WORK Much can still be examined about how to determine which programs will benefit from the optimization, and what changes should be made in a partial optimization such that they cause the greatest change in performance. The next goal is to develop an algorithm which finds where optimizations can be performed and performs the optimization automatically, whenever possible and beneficial. We are also considering an interactive tool that allows programmers to perform the specialization interactively when the fully automatic transformation is not possible. 6. REFERENCES Figure 1. Comparing Partial Quicksort Optimizations. 3.2 Results . The times we measured included time taken to insert the 3,000 elements into the two lists, then sort them using compareTo() with our Quicksort class. The Original implementation took about 15 seconds to do this for each run. The ArrayList implementation took about 12 seconds; all of our other optimizations took about [1] Daniel Selifonov, Nathan Dahlberg, Elena Machkasova. On the Algorithm for Specializing Java Programs with Generic Types. Midwest Instruction and Computing Symposium 2007 [2] Gilad Bracha. Generics in the Java Programming Language. Java Tutorials, etc., etc., look up specifics later. [3] The Java Language Specification. More bibliographical stuff here