- 191 -
o
Cluster serialized objects that are used together by putting them into the same file.
o
Put objects next to each other if they are required together.
o
Consider using an object-storage system such as an object database if your object- storage requirements are at all sophisticated.
•
Use compression when the overhead of compression is outweighed by the benefit of reducing IO.
o
Avoid compression when the system has a heavily loaded CPU.
o
Consider using intelligent IO classes that can decide to use compression on the fly.
o
Consider searching directly against compressed data without decompressing.
9.1 Avoiding Unnecessary Sorting Overhead
The JDK system provides sorting methods in
java.util.Arrays
for arrays of objects and in
java.util.Collections
for objects implementing the
Collection
interfaces. These sorts are usually adequate for all but the most specialized applications. To optimize a sort, you can normally
get enough improvement by reimplementing a standard sort such as quicksort as a method in the class being sorted. Comparisons of elements can then be made directly, without calling generic
comparison methods. Only the most specialized applications usually need to search for specialized sorting algorithms .
As an example, here is a simple class with just an
int
instance variable, on which you need to sort:
public class Sortable implements Comparable
{ int order;
public Sortableint i{order = i;} public int compareToObject o{return order - Sortable o.order;}
public int compareToSortableSortable o{return order - o.order;} }
I can use the
Arrays.sort
to sort this, but as I want to make a direct comparison with exactly the same sorting algorithm as I tune, I use an implementation of a standard quicksort . This
implementation is not shown here; for an example, see the quicksort implementation in Section
11.7 . The only modification to the standard quicksort will be that for each optimization, the
quicksort is adjusted to use the appropriate comparison method and data type. For example, a generic quicksort that sorts an array of
Comparable
objects is implemented as:
public static void quicksortComparable[] arr, int lo, int hi {
... int mid = lo + hi 2;
Comparable middle = arr[ mid ]; Comparable data type ...
uses Comparable.compareToObject ifarr[ lo ].compareTomiddle 0
... }
To start with, I use a quicksort that takes an array of
Object
s. The comparisons are made using the
Comparator.compareTo
method, so every
Object
in the array must implement the
Comparable
interface. Since every object is a
Comparable
, why dont I specify a
Comparable[]
instead of an
Object[]
in the quicksort signature? I use an
Object[]
signature initially, to illustrate why it is faster to use a
Comparable[]
signature.
java.util.Arrays.sort
has an
Object[]
as its
- 192 - argument rather than a
Comparable[]
because it needs to support any array type, and Java doesnt let you cast a generic array to a more specific array type. That is, you cannot use:
Object[] arr = new Object[10]; ... fill the array with Comparable objects
The following line does not compile Arrays.sort Comparable[] arr; NOT valid Java code, invalid cast
This means that if you specify a sort with the signature that accepts only a
Comparable[]
object array, then you actually have to create a new
Comparable
array and copy all your objects to that array. And it is often the case that your array is already in an
Object
array, hence the more generic but slower support in the JDK. Another option for the JDK would be to have a second copy of the
identical sort method in
java.util.Arrays
, except that the second sort would specify
Comparable[]
in the signature and have no casts in the implementation. This has not been done in
java.util.Arrays
up to JDK 1.3, but may be in the future. Back to the example. The first quicksort with the
Object[]
signature gives a baseline at 100. I am sorting a randomized array of
Sortable
objects, using the same randomized order for each test. Switching to a quicksort that specifies an array of
Comparable
objects which means you avoid casting every object for each comparison is faster for every VM I tested see
Table 9-1 . You can
modify the quicksort even further to cater specifically to
Sortable
objects, so that you call the
Sortable.compareToSortable
method directly. This avoids yet another cast, the cast in the
Sortable.compareTo
method, and therefore reduces the time even further. Table 9-1, Timings of the Various Sorting Tests Normalized to the Initial JDK 1.2 Test
1.2 1.2 no JIT 1.3
HotSpot 1.0 HotSpot 2nd Run
QuicksortObject[] 100 322 47 56
42 QuicksortComparable[]
64 242 43 51
39 QuicksortSortable[]
45 204 42 39
28 QuicksortSortable[] using field access
40 115 30 28
28 Arrays.sort
109 313 57 87 57
The last quicksort accepting a
Sortable[]
array looks like:
public static void quicksortSortable[] arr, int lo, int hi {
... int mid = lo + hi 2;
Sortable middle = arr[ mid ]; Sortable data type ...
uses Sortable.compareToSortableSortable ifarr[ lo ].compareToSortablemiddle 0
...
You can make one further improvement, which is to access the
Sortable.order
fields directly from the quicksort. The final modified quicksort looks like:
public static void quicksortSortable[] arr, int lo, int hi {
... int mid = lo + hi 2;
Sortable middle = arr[ mid ]; Sortable data type ...
uses Sortable.order field for direct comparison ifarr[ lo ].order - middle.order 0 -- same as next line
- 193 -
ifarr[ lo ].order middle.order ...
This last quicksort gives a further improvement in time see Table 9-1
. Overall, this tuning example shows that by avoiding the casts by implementing a standard sort algorithm and
comparison method specifically for a particular class, you can more than double the speed of the sort with little effort. For comparison, I have included in
Table 9-1 the timings for using the
Arrays.sort
method, applied to the same randomized list of
Sortable
objects used in the example. The
Arrays.sort
method uses a merge sort that performs better on a partially sorted list. Merge sort was chosen for
Arrays.sort
because, although quicksort provides better performance on average, the merge sort provides sort stability. A stable sort does not alter the order
of elements that are equal based on the comparison method used.
[1] [1]
The standard quicksort algorithm also has very bad worst-case performance. There are quicksort variations that improve the worst-case performance.
For more specialized and optimized sorts, there are books including Java-specific ones covering various sort algorithms, and a variety of sort implementations available on the Web. The computer
literature is full of articles providing improved sorting algorithms for specific types of data, and you may need to run a search to find specialized sorts for your particular application. A good place to
start is with the classic reference The Art of Computer Programming by Donald Knuth.
In the case of nonarray elements such as linked-list structures, a recursive merge sort is the best sorting algorithm and can be faster than a quicksort on arrays with the same dataset. Note that the
JDK
Collections.sort
methods are suboptimal for linked lists. The
Collections.sortList
method converts the list into an array before sorting it, which is the wrong strategy to sort linked lists, as shown in an article by John Boyer.
[2]
Boyer also shows that a binary search on a linked list is significantly better than a linear search if the cost of comparisons is
more than about two or three node traversals, as is typically the case.
[2]
Sorting and Searching Linked Lists in Java, Dr. Dobbs Journal, May 1998.
If you need your sort algorithm to run faster, optimizing the comparisons in the sort method is a good place to start. This can be done in several ways:
•
Eliminating casts by specifying data types more precisely.
•
Modifying the comparison algorithm to be quicker.
•
Replacing the objects with wrappers that compare faster e.g.,
java.text.CollationKey
s. These are best used when the comparison method requires a calculation for each object being compared, and that calculation can be cached.
•
Eliminating methods by accessing fields directly.
•
Partially presorting the array with a faster partial sort, followed by the full sort. Only when the performance is still short of your target do you need to start looking for alternatives.
Several of the techniques listed here have been applied in the earlier example, and also in the internationalized string sorting example in
Section 5.6 .
9.2 An Efficient Sorting Framework