Counting

The order equal keys keep

Four of the eight sorts here leave every pair of equal keys in the order it arrived in and four move between 1,209 and 2,403 pairs, and none of the four counts every plate here reports can tell them apart. Decorating each record with its arrival position makes any of them stable, for four thousand words and between 0.05 and 1.64 times its comparisons — a charge of 64% on Shellsort and a saving of 95% on quicksort, because the ties stability has to break are the ties a two-way partition chokes on.

The count somebody chose ranked ten sorting algorithms by six measured quantities and found the orders disagree about 91% of all pairs. Six counters, six rankings, and the conclusion that there is no ranking of sorting algorithms — there are six, and choosing between them is a statement about the data.

There is a seventh quantity, and none of the six can see it. A sort is stable when two records with equal keys come out in the order they went in, and two sorts making identical comparisons, swaps, reads and writes can differ on it. It is the property a caller asks for when sorting a table by one column after sorting it by another, and it is the reason the standard library of nearly every language ships the sort it does.

This page measures it: which sorts have it, what it is worth in the currency the other six use, and what it costs to buy from a sort that does not.

Making the property visible to a counter

The difficulty is that stability is a statement about records and every count on this collection is a statement about an array of numbers. Counting instead of timing is the instrument: an array that increments a tally each time it is read, written, compared or swapped. It has no notion of a record with two fields, so it cannot see whether two records with the same key came out in the order they arrived.

The repair is to put the arrival position into the number and take it back out afterwards. Each record becomes key×M+position\text{key} \times M + \text{position} for an MM larger than the array, and the sort is handed a comparator that divides by MM before comparing — so it sees genuine ties and its counts are exactly the counts it would make on bare keys, and the positions are still there in the sorted array to be read off. The adversary who knows the seed used the same door for a different purpose: a comparison sort is defined as one that learns about its input only through the comparator, so replacing the comparator is the one intervention that cannot change what the algorithm is.

Every plate below is that measurement, and the thing to notice about it is that it adds a seventh count to the six. It is not a count of operations; it is a count of the answer — how many pairs came out wrong — and it is the only quantity on this collection that measures what a sort produced rather than what it did.

Who moves equal keys

Each record is a key and its arrival position, and the sort is given a comparator that reads the key only — so it sees genuine ties, its counts are the counts it would make on bare keys, and afterwards the positions say what it did with them.

4 of these 8 sorts leave every pair of equal keys in the order it arrived in, and the other 4 move between 1,209 and 2,403 pairsPairs of equal keys left out of the order they arrived in, for 4,096 records over 8 distinct keys, with the share of each sort's comparisons that were ties. Merge sort: 42,325 comparisons of which 54.6% were ties, 0 equal pairs out of order. Merge sort with an insertion cutoff: 47,894 comparisons of which 46.9% were ties, 0 equal pairs out of order. Insertion sort: 3,745,731 comparisons of which 0.2% were ties, 0 equal pairs out of order. Bubble sort: 8,250,057 comparisons of which 54.5% were ties, 0 equal pairs out of order. Shellsort: 42,161 comparisons of which 71.1% were ties, 2,014 equal pairs out of order. Heapsort: 77,248 comparisons of which 31.5% were ties, 2,403 equal pairs out of order. Selection sort: 8,386,560 comparisons of which 23.5% were ties, 1,209 equal pairs out of order. Quicksort, median-of-three pivot: 1,071,653 comparisons of which 99.2% were ties, 1,428 equal pairs out of order.Merge sortnone · 55% tiesMerge sort with an insertion cutoffnone · 47% tiesInsertion sortnone · 0% tiesBubble sortnone · 55% tiesShellsort2,014 · 71% tiesHeapsort2,403 · 32% tiesSelection sort1,209 · 24% tiesQuicksort, median-of-three pivot1,428 · 99% tiesequal pairs left out of the order they arrived in4,096 records, 8 distinct keysdark: the sorts that move them
Fig. 1 Pairs of equal keys left out of the order they arrived in, for 4,096 records over eight distinct keys. Merge sort, merge sort with an insertion cutoff, insertion sort and bubble sort move none. Shellsort moves 2,014, heapsort 2,403, selection sort 1,209 and median-of-three quicksort 1,428. The share of each sort’s comparisons that were ties runs from 0.2% for insertion sort to 99% for quicksort.

Four and four, and the division does not follow any of the six counts. Merge sort at 42,325 comparisons and bubble sort at 8,250,057 are both stable; heapsort at 77,248 and selection sort at 8,386,560 are both not. The property is decided by whether the algorithm ever moves an element past an equal one, and that is a question about which positions it swaps rather than about how many.

The two quadratic sorts are the clearest pair. Bubble sort compares adjacent elements and swaps only when strictly out of order, so an equal pair is never exchanged; selection sort finds the minimum of the remaining range and swaps it into place, and that swap throws whatever was in the way to the far end of the range, past every equal element between. Same asymptotic class, same order of comparisons, opposite answers, and the reason is one line of each.

The ties, and how many there are

The comparisons that decide stability are the ties, and how many there are is a property of the data rather than of the sort.

The share of comparisons that are ties — the comparisons a stable sort has to break — falls from 77% at 2 distinct keys to 18% at 1,024The share of a sort's comparisons that compare two equal keys, against how many distinct keys the 4,096 records hold, on logarithmic axes. Every one of those comparisons is one a lexicographic comparator would have to break with a second field. Merge sort: 76.5% at 2 distinct (0 pairs out of order), 62.6% at 4 distinct (0 pairs out of order), 48.6% at 16 distinct (0 pairs out of order), 38.7% at 64 distinct (0 pairs out of order), 28.9% at 256 distinct (0 pairs out of order), 17.9% at 1024 distinct (0 pairs out of order). Heapsort: 48.9% at 2 distinct (2,570 pairs out of order), 37.3% at 4 distinct (2,377 pairs out of order), 27.3% at 16 distinct (2,395 pairs out of order), 21.0% at 64 distinct (2,348 pairs out of order), 15.5% at 256 distinct (2,260 pairs out of order), 9.4% at 1024 distinct (1,872 pairs out of order). Selection sort: 75.6% at 2 distinct (703 pairs out of order), 43.8% at 4 distinct (1,019 pairs out of order), 12.2% at 16 distinct (1,299 pairs out of order), 3.1% at 64 distinct (1,326 pairs out of order), 0.8% at 256 distinct (1,245 pairs out of order), 0.2% at 1024 distinct (1,020 pairs out of order).24166425610240.010.1distinct keys among the recordsshare of comparisons that are tiesMerge sortHeapsortSelection sort4,096 recordsa tie is a comparison stability has to decide
Fig. 2 The share of a sort’s comparisons that compare two equal keys, against how many distinct keys the 4,096 records hold, on logarithmic axes. For merge sort: 77% at two distinct keys, 48% at sixteen, 18% at 1,024. Heapsort and selection sort are lower throughout and fall the same way.

At two distinct keys three comparisons in four are ties; at a thousand, one in five. That is the axis every statement about stability lives on, and it is worth being explicit that it is not the axis a sort is usually measured on. A benchmark of random distinct keys has almost no ties and cannot distinguish a stable sort from an unstable one by any count at all — the two make the same comparisons, the same swaps, and produce the same array.

Which is why the question is usually settled by reading an implementation rather than by measuring. This page’s whole method is a way of making the property visible to a counter: give the records tags, and the counter sees what happens to ties instead of being told.

The share of comparisons that are ties — the comparisons a stable sort has to break — falls from 100% at 2 distinct keys to 30% at 1,024The share of a sort's comparisons that compare two equal keys, against how many distinct keys the 4,096 records hold, on logarithmic axes. Every one of those comparisons is one a lexicographic comparator would have to break with a second field. Quicksort, median-of-three pivot: 100.0% at 2 distinct (705 pairs out of order), 99.9% at 4 distinct (1,142 pairs out of order), 97.5% at 16 distinct (1,575 pairs out of order), 86.7% at 64 distinct (1,813 pairs out of order), 59.8% at 256 distinct (1,831 pairs out of order), 29.6% at 1024 distinct (1,533 pairs out of order). Shellsort: 97.2% at 2 distinct (1,709 pairs out of order), 83.7% at 4 distinct (1,924 pairs out of order), 59.1% at 16 distinct (2,029 pairs out of order), 39.0% at 64 distinct (2,019 pairs out of order), 24.1% at 256 distinct (1,916 pairs out of order), 13.2% at 1024 distinct (1,548 pairs out of order). Bubble sort: 65.7% at 2 distinct (0 pairs out of order), 59.0% at 4 distinct (0 pairs out of order), 51.7% at 16 distinct (0 pairs out of order), 48.3% at 64 distinct (0 pairs out of order), 43.4% at 256 distinct (0 pairs out of order), 30.2% at 1024 distinct (0 pairs out of order).24166425610241distinct keys among the recordsshare of comparisons that are tiesQuicksort, median-of-three pivotShellsortBubble sort4,096 recordsa tie is a comparison stability has to decide
Fig. 3 The same sweep for three sorts whose tie shares run far higher. Median-of-three quicksort compares two equal keys on 100.0% of its comparisons at two distinct keys, 97.5% at sixteen and 29.6% at a thousand; Shellsort on 97.2%, 59.1% and 13.2%; bubble sort between. A sort whose comparisons are mostly ties is a sort whose behaviour on this input is decided by what it does with them.

Quicksort’s 97.5% at sixteen distinct keys is the number to carry into the sections below. Nearly every comparison a two-way partition makes on data with few distinct keys is between two equal keys, which means nearly every comparison it makes decides nothing about where anything goes — and the sort still has to make it. The pairs it leaves out of order barely move across the whole sweep, from 705 at two distinct keys to 1,533 at a thousand, because the number of equal pairs available to misplace rises and the chance of misplacing each one falls.

Stability is a rule, not a structure

The cheapest demonstration of that is merge sort with one character changed.

The same merge sort with one character changed: 42,325 comparisons and no equal pair moved, against 42,285 and 4,088 movedEqual pairs left out of the order they arrived in, for two merge sorts of the same 4,096 records over 8 distinct keys. One takes from the left half when the two fronts are equal and the other from the right; everything else about them is identical. Merge sort: 42,325 comparisons, 133,802 reads, 49,152 writes, 23,096 of the comparisons ties, 0 equal pairs out of order. Merge sort, right half on a tie: 42,285 comparisons, 133,722 reads, 49,152 writes, 23,136 of the comparisons ties, 4,088 equal pairs out of order.Merge sort0 pairsMerge sort, right half on a tie4,088 pairsequal pairs left out of the order they arrived in4,096 records, 8 distinct keysidentical counts, different answers
Fig. 4 Two merge sorts of the same 4,096 records over eight distinct keys. One takes from the left half when the two fronts are equal — 42,325 comparisons, 133,802 reads, 49,152 writes, no equal pair moved. The other takes from the right — 42,285 comparisons, 133,722 reads, the same 49,152 writes, and 4,088 equal pairs moved.

Forty comparisons apart, one in a thousand, and one of them is stable and the other is not. Nothing structural distinguishes them: the recursion is the same, the buffer is the same, the merge is the same loop with < where the other has .

So stability is not a property a sort has by being the kind of sort it is. Merge sort is stable because its merge prefers the left half on a tie, and a merge that preferred the right would be a merge sort that is not. The same is true of the other three on the first plate: insertion sort stops shifting when it meets an equal element, bubble sort swaps only on strict inequality, and each would lose the property if it did not.

That also explains what happens to the counts. Taking the right half on a tie makes the merge take a different branch on 23,000 comparisons, which changes the run lengths slightly and the comparison count by 0.09%. The property costs nothing and the choice is invisible in every count — which is exactly why it has to be specified rather than measured.

Buying it from a sort that does not have it

A caller who needs stability and an unstable sort has a standard repair: decorate each record with its arrival position and compare the pair lexicographically. Ties on the key are broken by the position, no two decorated records are equal, and any correct sort then produces the stable order.

Decorating every record with its position makes any sort stable, and costs it between 0.05 and 1.64 times its comparisons — a saving of 95% on one sort and a charge of 64% on anotherComparisons made after decorating each of 4,096 records with its arrival position, divided by the comparisons made without it, over 8 distinct keys. Decoration also costs 4,096 words of tag, which is the same for every sort and is not drawn. Every decorated sort leaves no equal pair out of order. Merge sort: 42,325 comparisons undecorated and 42,325 decorated, 1.000 times; 0 equal pairs were out of order before. Merge sort with an insertion cutoff: 47,894 comparisons undecorated and 47,894 decorated, 1.000 times; 0 equal pairs were out of order before. Insertion sort: 3,745,731 comparisons undecorated and 3,745,731 decorated, 1.000 times; 0 equal pairs were out of order before. Bubble sort: 8,250,057 comparisons undecorated and 8,250,057 decorated, 1.000 times; 0 equal pairs were out of order before. Shellsort: 42,161 comparisons undecorated and 69,072 decorated, 1.638 times; 2,014 equal pairs were out of order before. Heapsort: 77,248 comparisons undecorated and 85,921 decorated, 1.112 times; 2,403 equal pairs were out of order before. Selection sort: 8,386,560 comparisons undecorated and 8,386,560 decorated, 1.000 times; 1,209 equal pairs were out of order before. Quicksort, median-of-three pivot: 1,071,653 comparisons undecorated and 54,737 decorated, 0.051 times; 1,428 equal pairs were out of order before.0.05×0.1×0.25×0.5×Merge sort1.00×Merge sort with an insertion cutoff1.00×Insertion sort1.00×Bubble sort1.00×Shellsort1.64×Heapsort1.11×Selection sort1.00×Quicksort, median-of-three pivot0.05×comparisons after decorating ÷ comparisons before — one is no change4,096 records, 8 distinct keysevery decorated sort is stable
Fig. 5 Comparisons after decorating each of 4,096 records with its arrival position, divided by the comparisons without it, over eight distinct keys. Merge sort, its hybrid, insertion sort, bubble sort and selection sort: 1.00×, unchanged. Heapsort: 1.11×. Shellsort: 1.64×. Median-of-three quicksort: 0.05×. Every decorated sort leaves no equal pair out of order.

The space is the same for every sort — 4,096 words of tag, one per record, which for eight-byte records is a fifty per cent increase in the data — and the comparisons are not the same at all.

Five sorts pay nothing. Insertion, selection and bubble sort make a number of comparisons that does not depend on the answers; merge sort and its hybrid take the same branches on the decorated keys as their tie rule already took. For these, stability by decoration costs nn words and nothing else.

Heapsort pays eleven per cent and Shellsort sixty-four. Both use ties: a sift that stops on an equal element does less work than one that does not, and breaking every tie takes that away.

And quicksort is paid ninety-five per cent. That is the finding worth the page.

Why decorating makes quicksort twenty times faster

Median-of-three quicksort on 4,096 records over eight distinct keys makes 1,071,653 comparisons. Decorated, it makes 54,737 — a factor of 19.6.

The mechanism is the one three-way partitioning exists for. A two-way partition splits the range into “less than the pivot” and “not less”, and a range in which most elements equal the pivot goes almost entirely into one side. With eight distinct keys among four thousand records, every pivot equals about an eighth of its range, and the partition is repeatedly lopsided for a reason no pivot rule can fix — median-of-three picks a pivot in the middle of the values, and the middle value still has five hundred copies.

Decorating removes that. The keys become distinct, every pivot splits its range near the middle, and quicksort runs at its expected 1.39nlog2n1.39 n \log_2 n.

So the decoration is doing two things at once and only one of them was asked for. It buys stability, which is the point; and it removes duplicate keys, which is a repair for a pathology quicksort has and the other sorts do not. The 0.05× is not a fact about stability. It is a fact about how badly a two-way partition handles ties, measured by an experiment that happened to remove them.

That is worth stating carefully because the arithmetic invites the wrong conclusion. A caller choosing quicksort for data with few distinct keys should not decorate in order to go faster; they should use a three-way partition, which costs no extra space and removes the same pathology. The decoration is the right repair for stability and an accidental repair for something else.

The four that ship

The sorts above are the textbook ones. The four a real standard library runs make the same division, and they make it in a way that decides something.

Decorating every record with its position makes any sort stable, and costs it between 0.56 and 2.38 times its comparisons — a saving of 44% on one sort and a charge of 138% on anotherComparisons made after decorating each of 4,096 records with its arrival position, divided by the comparisons made without it, over 8 distinct keys. Decoration also costs 4,096 words of tag, which is the same for every sort and is not drawn. Every decorated sort leaves no equal pair out of order. Timsort: 28,489 comparisons undecorated and 28,489 decorated, 1.000 times; 0 equal pairs were out of order before. Introsort: 112,734 comparisons undecorated and 62,744 decorated, 0.557 times; 1,396 equal pairs were out of order before. Dual-pivot quicksort: 36,190 comparisons undecorated and 51,259 decorated, 1.416 times; 1,769 equal pairs were out of order before. pdqsort: 22,588 comparisons undecorated and 53,872 decorated, 2.385 times; 2,093 equal pairs were out of order before.0.5×Timsort1.00×Introsort0.56×Dual-pivot quicksort1.42×pdqsort2.38×comparisons after decorating ÷ comparisons before — one is no change4,096 records, 8 distinct keysevery decorated sort is stable
Fig. 6 The same measurement for the four sorts real libraries ship. Timsort: 28,489 comparisons undecorated and 28,489 decorated, no equal pair out of order either way. Introsort: 112,734 and 62,744, 0.56×, 1,396 pairs moved. Dual-pivot quicksort: 36,190 and 51,259, 1.42×, 1,769 moved. pdqsort: 22,588 and 53,872, 2.38×, 2,093 moved.

pdqsort is the cheapest of the four on this data and the dearest as soon as stability is asked for. Undecorated it makes 22,588 comparisons against Timsort’s 28,489 — twenty-one per cent fewer. Decorated it makes 53,872 against Timsort’s unchanged 28,489 — eighty-nine per cent more.

The reason is that pdqsort’s speed on this input is its handling of ties: it detects ranges of equal elements and skips them, which is the partitioning repair the previous section said quicksort needs and pdqsort has. Decorating makes every key distinct and there is nothing left to detect, so the repair cannot fire and the sort pays the ordinary price. A sort optimised for duplicate keys is a sort that loses everything when the duplicates are taken away, and taking them away is exactly what decoration does.

So a library choosing between them faces a question with no single answer. If the caller does not need stability, pdqsort is twenty per cent cheaper. If the caller needs it — and a library sorting objects by a field usually assumes they might — Timsort is forty-seven per cent cheaper than the alternative and needs no tag. The sort the library ships is the page that measures what those libraries actually chose; this is one of the numbers behind the choice, and it is not a number any of the six counts reports.

What is being counted, and what a caller pays

Three quantities separate here and only the first is what the earlier pages count.

Comparisons move between 0.05× and 1.64× and the direction depends on whether a sort was exploiting ties or suffering from them.

Space is nn words, for every sort, and that is usually the binding cost. The exchange rate nobody wrote down measured what happens to the ranking when a record grows: selection sort goes from second-worst of seven at four bytes a record to best of seven at five hundred and twelve, because its data movement is what a large record makes expensive. A tag is four or eight bytes on every record, and on small records it is the difference between a cache line holding eight of them and holding five.

And the comparator’s own cost. A lexicographic comparator reads a second field on every tie, which on this data is between a fifth and 99% of all comparisons. Every count on this collection charges one unit per comparison, and the comparison that is not one comparison is the standing argument that the unit is a choice. Here the choice hides a term that the tie share makes large exactly where stability matters most.

What is not measured here

Uniform keys. The records draw their keys uniformly from the distinct values, so every key has about the same number of copies. A distribution with one very common key and a long tail — which is what real data looks like — would have the same tie share concentrated in different places, and quicksort’s collapse would be worse.

One repair for stability. Decoration is the general one. Particular sorts have cheaper ones: heapsort can be made stable by a different sift rule at some cost in moves, and a quicksort can be made stable by partitioning out of place into a buffer, which is nn words again and a different nn words. Neither is measured.

Comparisons, not comparator calls. A decorated comparison that reads only the key is counted the same as one that reads the key and the tag. The tie counts are reported so a reader can apply their own weighting, and no plate applies one.

Pairs out of order, not a distance. A sort that moves one record past two thousand equal ones and a sort that swaps two thousand adjacent equal pairs both report two thousand. What a caller notices is usually the first, and a worst case ten positions wide is the page that argues a count of misplacements is a poor description of how far an arrangement is from the one wanted.

No deletions from the definition. Stability is defined here on a single sort of a single array. The reason callers want it is that it composes — sorting by one key and then by another leaves the first as a tiebreak — and nothing here measures a composition.

One size. Four thousand records throughout. The tie share at a fixed number of distinct keys rises with nn, since more records share each key, so every effect on this page is larger on a larger table and the eight-distinct-key column is a mild case rather than an extreme one.

One record shape. The tag is a word and the key is a word. Nothing about the plates changes if the record is a hundred bytes; what changes is which of the three costs above dominates, and it is the second.

Still open: the sort that is stable and does not pay for it

Every stable sort on the first plate uses extra space or quadratic time. Merge sort’s nn-word buffer is exactly the space decoration would have cost, so a caller who wanted stability paid for it either way; insertion and bubble sort are in place and quadratic. The two sorts here that are fast and in place — heapsort and quicksort — are both unstable, and nothing on this page says whether that is a coincidence.

It is not known to be. In-place stable merging is possible in O(nlogn)O(n \log n) comparisons with O(1)O(1) extra space, by algorithms that are considerably more intricate than a merge and are not what any library ships. The question a measurement can answer is what the intricacy costs in the currencies this collection counts.

The measurement that follows implements one of them — the block-swapping in-place merge that a symmetric rotation gives — and sets it against the buffered merge on the same records: comparisons, moves, peak auxiliary space, and equal pairs moved. The prediction from the literature is that the comparisons are within a small factor and the moves are several times more, which would put it on the frontier the frontier between time and space drew rather than above it — and would say that the reason libraries buffer is a constant rather than a class.

Named alongside this one

Essays reaching for the same objects. Nobody chose these; they are what the concept index makes visible.

What links here

Every essay whose body links to this one.

The objects this essay names

Each one links to every other essay that touches it.

Comparison countCounting conventionHeapMeasurement designMerge sortQuicksortRecord sizeSpace time tradeSpecificationStabilityThree-way partition