- 200 -
} }
quicksortsources, orders, lo, left; quicksortsources, orders, left + 1, hi;
} }
With this optimization, the framework quicksort is now as fast as the fastest handcrafted quicksort from the previous section see
Table 9-2 .
Table 9-2, Timings of the Various Sorting Tests Normalized to the Initial JDK 1.2 Test of Table 9-1
1.2 1.2 no
JIT
1.3 HotSpot
1.0 HotSpot 2nd
Run QuicksortObject[] from
Table 9-1 100 322 47
56 42
QuicksortSortable[] using field access from Table
9-1 40 115
30 28 28
ArrayQuickSorter using Sortable.field 36 109
49
[3]
60 31 Arrays.sort from
Table 9-1 109 313 57
87 57
[3]
The HotSpot server version manages to optimize the framework sort to be almost as fast as the direct field access sort. This indicates that the 1.3 VM, which uses HotSpot technology, is theoretically capable of similarly optimizing the framework sort. That it hasnt managed to in JDK 1.3 indicates that the VM can be
improved further.
9.3 Better Than Onlogn Sorting
Computer-science analysis of sorting algorithms show that, on average, no generic sorting algorithm can scale faster than Onlogn see
Orders of Magnitude . However, many applications
dont require a general sort. You often have additional information that can help you to improve the speed of a particular sort.
Orders of Magnitude
When discussing the time taken for particular algorithms to execute, it is important to know not just how long the algorithm takes for a particular dataset, but also how long it
takes for different-sized datasets, i.e., how it scales . For applications, the problems of handling 10 objects and handling 10 million objects are often completely different
problems, not just different-sized versions of the same problem.
One common way to indicate the behavior of algorithms across different scales of datasets is to describe the algorithms scaling characteristics by the dominant numerical
function relating to the scaling behavior. The notation used is Ofunction, where function is replaced by the dominant numerical scaling function. It is common to use the
letter n to indicate the number of data items being considered in the function. For example, On indicates that the algorithm under consideration increases in time linearly
with the size of the dataset. On
2
indicates that the time taken increases according to the square of the size of the dataset.
These orders of magnitude do not indicate absolute times taken by the algorithm. Instead, they indicate how much longer the algorithm takes when the dataset increases in size. If
an On algorithm takes 200 seconds for n=10, it will take about 2000 seconds for n=100, i.e., a tenfold increase in the dataset size implies a tenfold increase in the amount of time
- 201 - taken by the algorithm. An On
2
algorithm might take 5 seconds for n=10, and about 500 seconds for n=100, i.e., a tenfold increase in the dataset size implies a hundredfold
increase in the time taken by the algorithm. Note that the scaled times are approximate, as the order-of-magnitude statistics include only the dominant scaling function, and there
may be other smaller terms that adjust the actual time taken.
The order of magnitude does not indicate the relative speeds of two different algorithms for any specific dataset size. Instead, the order-of-magnitude statistics indicate how
expensive one particular algorithm may be as your dataset grows. In the examples of the last paragraph, the time taken for the second On
2
algorithm increases much faster than the first On algorithm, but the On
2
algorithm is still faster at n=100. However, by n=1000, it would be the slower of the two algorithms 50,000 seconds compared to
20,000 seconds for the On algorithm.
To take a concrete example, hash tables have an O1 order of magnitude for accessing elements. This means that the time taken to access elements in a hash table is independent
of the size of the hash table. Accessing elements in an array by linearly searching through that array takes On. In absolute times, it might be quicker to execute the linear array
search on a small array than to access from a hash table. But as the number of elements became larger, at some point the hash table will always become quicker.
For example, if you have 1000 items to sort, and each item can be given a unique ordering value that corresponds to a unique integer between 1 and 1000, the best sort is simply to slot the items
directly into their correct locations in an array. No comparisons between the items are necessary.
Of course, typically you cant map your elements so neatly. But if you can map items to integer keys that are more or less evenly distributed, you can still take advantage of improved sorting
characteristics. Bear in mind that an array of partially sorted items can be sorted faster than a typical unsorted array.
When you can guess the approximate final position of the items in the collection to be sorted, you can use this knowledge to improve sorting speed. You should specifically look out for sorts where:
•
Items can be given an ordering value that can be mapped to integer keys.
•
The distribution of the keys is regular, or any one of the following is true:
o
The distribution of the keys is fairly even, so that when mapped into array indexes, ordering is approximately kept.
o
The keys have evenly distributed clusters.
o
The distribution of the keys has a mapping into one of these other distributions. The distribution of the keys is fairly critical. A regular distribution allows them to be mapped
straightforwardly into array indexes. An uneven distribution is difficult to map. But if you have an uneven distribution and can specify a mapping that allows you to flatten out the keys in some way,
it may still be possible to apply this methodology. For example, if you know that your keys will have a normal bell-curve distribution, you can apply an inverse bell-curve function to the keys to
flatten them out to array indexes.
For this technique to work, the mapped keys do not need to be unique. Several keys or groups of keys can map to the same value or values. Indeed, it is quite difficult to make the index mapping
unique. You need to be aware of this and handle the resulting collisions. Normally, you can map clusters of keys into subsections of the sorted array. These subsections are probably not internally
sorted, but they may be correctly sorted against each other i.e., all elements in subsection 1 are
- 202 - ordered below all elements in subsection 2, all elements in subsection 2 are ordered below all
elements in subsection 3, etc.. This way, the problem has been modified to sort multiple smaller subsections which is faster than sorting the whole array, and hence the array is sorted more
quickly.
Note that
Object.hashCode
provides a mechanism for generating an integer for any object. However, the resulting hash code is not guaranteed to be evenly distributed or even unique, nor is it