Counting

The count somebody chose

Six quantities can now be measured for every sort. Ranking the ten algorithms by each of them and comparing the orders, comparisons and peak space disagree about 91% of all pairs, and memory traffic and modelled misses disagree about 7%. There is no ranking of sorting algorithms; there are six, and choosing between them is a statement about the data rather than about the algorithms.

An earlier essay took one run of each sorting algorithm and reported four counts from it — comparisons, swaps, reads and writes — and showed that they rank the algorithms differently. The site has since acquired two more countable quantities: modelled cache misses, and peak auxiliary space.

Six quantities, one run, ten algorithms. Ranking the algorithms by each quantity gives six orderings, and the question this essay is about is how far apart they are.

Far enough that “which sorting algorithm is best” is not a question with a hidden answer. It is a question with six answers, and the disagreement between two of them is close to total.

Comparisons and swaps at n = 512Selection sort performs the most comparisons of any algorithm here and among the fewest swaps — it never moves an element it does not have to. Ordering these algorithms by comparisons and ordering them by swaps gives two different orders, which is why the question "how many operations" needs the operation named before it has an answer.comparisonsswapsInsertion sort63,071 / 0Selection sort130,816 / 504Bubble sort129,688 / 62,563Merge sort3,964 / 0Heapsort7,653 / 4,170Quicksort5,049 / 2,380n = 512, random inputcounted in the same run
Fig. 1 Two of the six, at n = 512 rather than the 4,096 the table below uses. Ordering by the blue bars and ordering by the orange bars produces different orders — which was the earlier essay’s whole point, and is now one cell of a larger table.

The rankings

Every sort at n=4,096n = 4{,}096 on random input, ranked 1 to 10 by each quantity, with 1 the cheapest. Traffic is reads plus writes; misses are modelled with 64 lines of 8 elements; peak is the largest number of scratch slots and stack frames held at once.

algorithm comparisons swaps writes traffic misses peak space
merge sort 1 2 3 4 3 10
merge sort with a cutoff 2 4 4 5 4 9
quicksort, random pivot 3 8 6 3 2 7
quicksort, median of three 4 6 2 1 1 6
quicksort, first-element 5 7 5 2 5 8
Shellsort 6 3 7 6 6 5
heapsort 7 9 8 7 7 4
insertion sort 8 1 9 8 8 1
bubble sort 9 10 10 10 10 3
selection sort 10 5 1 9 9 2

Two rows are worth stopping on before any statistics.

Selection sort is first by writes and last by comparisons. It writes 8,158 times to sort 4,096 elements — two writes per element, because it finds the minimum by looking and then puts it in place — and it compares 8,386,560 times. By one measure it is the best algorithm on the page and by another it is the worst, and both measures are exact counts of the same run.

Insertion sort is first by swaps and eighth by comparisons. It records zero swaps, not because it moves nothing but because it moves data by shifting with get and set rather than by exchanging pairs. The swap counter measures a particular way of moving data and an algorithm that avoids the primitive shows up as free.

How far apart the rankings are

A rank order can be compared with another by counting the pairs of algorithms they put in different relative orders. Zero percent means identical rankings; fifty percent means no relationship at all; a hundred percent would be exact reversal.

swaps writes traffic misses peak
comparisons 40% 29% 20% 13% 91%
swaps 38% 47% 44% 56%
writes 27% 24% 71%
traffic 7% 76%
misses 78%

The two extremes are the useful part.

Comparisons and peak space disagree about 91% of pairs. That is very nearly a reversal, and it has a cause: the algorithms that compare least are the ones that buy their efficiency with a buffer, and the ones that hold nothing are the quadratic sorts. The two quantities are close to being negatives of one another over this set of algorithms, which is exactly why the frontier between them has five points on it rather than one.

Memory traffic and modelled misses disagree about 7%. One pair out of forty-five. These two are nearly the same measurement, which is worth knowing because they are usually presented as different levels of the story — the operation count and the machine behind it. On this set, at this size, the cache model adds almost nothing to what reads-plus-writes already said.

That second result is uncomfortable for a site that carries the cache model as its second independent quantity, and it deserves the two qualifications that make it survivable rather than a quiet embarrassment. The model’s parameters are fixed here at 64 lines of 8, so the working set at n=4,096n = 4{,}096 is eight times capacity for every algorithm; at other sizes and other capacities the two separate, which is what the cliff figure is about. And the pairs the two do order differently are the interesting ones — the ones where locality rather than volume decides — which is a small number of important cases rather than a large number of unimportant ones.

Both of those are true and neither makes 7% into a large number. The honest statement is that on this workload the second count earns much less than the fourth column of the ranking table suggests, and the case for carrying it rests on the sizes and parameters where it does not.

The same two counters, four more ways

Choosing a counter is choosing a ranking, and the demonstration is only as good as the settings it was taken at.

Comparisons and swaps at n = 64Selection sort performs the most comparisons of any algorithm here and among the fewest swaps — it never moves an element it does not have to. Ordering these algorithms by comparisons and ordering them by swaps gives two different orders, which is why the question "how many operations" needs the operation named before it has an answer.comparisonsswapsInsertion sort979 / 0Selection sort2,016 / 58Bubble sort2,001 / 918Merge sort306 / 0Heapsort578 / 341Quicksort429 / 199n = 64, random inputcounted in the same run
Fig. 2 Sixty-four elements, where the quadratic sorts have not yet separated from the others and the two counters still rank them differently.
Comparisons and swaps at n = 256Selection sort performs the most comparisons of any algorithm here and among the fewest swaps — it never moves an element it does not have to. Ordering these algorithms by comparisons and ordering them by swaps gives two different orders, which is why the question "how many operations" needs the operation named before it has an answer.comparisonsswapsInsertion sort15,647 / 0Selection sort32,640 / 250Bubble sort32,585 / 15,395Merge sort1,722 / 0Heapsort3,338 / 1,842Quicksort2,403 / 1,002n = 256, random inputcounted in the same run
Fig. 3 Half the original size. The two rankings disagree here too, about a different pair.

The other axis is the input, and it moves both counters — not by the same amount and not for the same algorithms.

Comparisons and swaps at n = 512Selection sort performs the most comparisons of any algorithm here and among the fewest swaps — it never moves an element it does not have to. Ordering these algorithms by comparisons and ordering them by swaps gives two different orders, which is why the question "how many operations" needs the operation named before it has an answer.comparisonsswapsInsertion sort511 / 0Selection sort130,816 / 0Bubble sort511 / 0Merge sort2,304 / 0Heapsort7,958 / 4,464Quicksort4,363 / 512n = 512, already sorted inputcounted in the same run
Fig. 4 Already sorted input at the original size.
Comparisons and swaps at n = 512Selection sort performs the most comparisons of any algorithm here and among the fewest swaps — it never moves an element it does not have to. Ordering these algorithms by comparisons and ordering them by swaps gives two different orders, which is why the question "how many operations" needs the operation named before it has an answer.comparisonsswapsInsertion sort55,019 / 0Selection sort130,816 / 446Bubble sort128,605 / 54,508Merge sort3,837 / 0Heapsort7,082 / 3,718Quicksort19,718 / 1,583n = 512, few distinct values inputcounted in the same run
Fig. 5 And input with few distinct values, where the partitioning sorts behave quite differently from the way they behave on distinct keys. On none of these four plates does one counter’s ranking reproduce the other’s.

The disagreements move with n

A table of disagreements at one size invites the question of whether they hold at others, and for one pair they emphatically do not.

Restricting to the seven linearithmic sorts — the quadratic three cannot be traced at large nn, because bubble sort at 32,768 elements generates over a hundred million accesses — and sweeping the size:

n comparisons vs misses traffic vs misses comparisons vs peak miss rates
512 38% 71% 95% 0.2% – 0.4%
2,048 29% 19% 100% 1.5% – 3.8%
8,192 29% 0% 95% 2.3% – 5.6%
32,768 14% 10% 81% 2.7% – 7.6%

The right-hand column explains the left ones. The modelled cache holds 512 elements, so at n=512n = 512 everything fits: every algorithm’s miss rate is between 0.2% and 0.4%, the differences between them are compulsory misses and rounding, and the ranking they produce is noise. That is why traffic and misses disagree about 71% of pairs at that size — the second column is not measuring anything, so it disagrees with everything at about the rate two random orderings would.

Above the cliff the miss rates separate — heapsort at 7.6% against merge sort’s 2.8% at n=32,768n = 32{,}768 — and the ranking becomes stable and close to the traffic ranking.

Two lessons, and the first repairs a claim made three sections above.

The 7% agreement between traffic and misses is a fact about one size. It runs 71%, 19%, 0%, 10% across the sweep, and the low numbers all come from sizes above the cache’s capacity. The honest version of the earlier claim is that above the cliff the two counts agree closely, and below it the second count has nothing to say.

A count can be uninformative rather than wrong. At n=512n = 512 the miss counts are exact, reproducible and correct, and they carry no information about which algorithm is better because the answer is “none of them, at this size”. A measurement that is precise and empty looks exactly like a measurement that is precise and useful, and the only way to tell is to know what the model’s parameters are — which is why the site prints them on every figure.

The comparisons-against-space column comes with its own qualification. It runs 95%, 100%, 95%, 81% over these seven algorithms, against 91% for all ten at n=4,096n = 4{,}096 — a different population, so the numbers are not directly comparable, and the conclusion is the same either way. That disagreement is near-total at every size measured and is not a single number.

Where each algorithm looks, and whenEvery array access from one run of each algorithm on 256 random elements: time along the horizontal axis, array index up the vertical. Merge sort makes 49% of its accesses to the next element or the same one; Heapsort makes 15%. That difference is invisible in the comparison count and is most of what the machine feels.Merge sort49% sequential · 7,540 accesses2560Heapsort15% sequential · 14,044 accesses2560time (accesses, left to right) · index (bottom to top)one run each, n = 256every access plotted
Fig. 6 Why the fifth column exists at all. Two algorithms’ access traces, index against time. Nothing in the first four columns of the ranking table contains this picture, and the modelled miss count is an attempt to reduce it to a number.

What a cost model is

The six columns are not six opinions. They are six cost models, and a cost model is a weighting over operations: how much a comparison costs relative to a write relative to a cache miss.

Written that way, the published complexity of a sorting algorithm is a cost model in which the weight on comparisons is one and every other weight is zero. That is a defensible choice — comparisons are the operation the lower bound is about, they are machine-independent, and for sorting integers they are a reasonable proxy for everything else — and it is a choice, and it is almost never stated as one.

Choosing a different weighting is not a refinement, it is a different question, and the answer changes:

Sorting 32-bit integers. A comparison and a move cost about the same, so the traffic column is the right one, and quicksort with median-of-three wins it.

Sorting 200-byte records by value. A move is fifty times a comparison. The write column dominates, and selection sort — last by comparisons — is doing 8,158 writes where merge sort does 49,152 and bubble sort does 8,384,368. It is still the wrong algorithm, because 8.4 million comparisons is 8.4 million, but the reason it is wrong has changed and the margin has narrowed by a factor of fifty.

Sorting pointers to objects. Every comparison chases a pointer to somewhere unrelated, so a comparison is itself a probable cache miss and the comparison count becomes a miss count. The misses column and the comparisons column merge.

Sorting something that barely fits in memory. The peak-space column is not a cost at all, it is a feasibility constraint, and every algorithm in the bottom rows of it is simply unavailable.

Four workloads, four different columns, one set of measurements. The columns are properties of the algorithms; which column matters is a property of the data, and no amount of measuring algorithms determines it.

What the site does about it

Three procedural things, and they are the whole of the method.

Report the vector rather than a number. Every figure names the quantity it plots in its caption strip. A bar chart of “cost” would be a bar chart of whichever column the author preferred, and there is no such column.

Draw the frontier where two quantities matter together. Comparisons against peak space has five algorithms on the frontier and five dominated, and being dominated — beaten on both counts at once — is the one statement that survives not knowing the weighting.

Never sum the columns. They are in different units and no addition of them means anything. The one place this site does sum operations is the graph field’s work, which adds four graph primitives into one number, and every figure that uses it says so explicitly because it is exactly the sin this essay is about. It is done there because the four primitives are all “one step of the traversal” in a way that a comparison and a cache miss are not, and it is still a choice.

A weighted sum is not the answer either

The obvious way out of six rankings is to combine them: choose weights, sum, rank once. It is what a benchmark does implicitly and it is worth saying why this site does not do it explicitly.

The weights are the answer, not an input to it. Choosing a weight for a cache miss relative to a comparison is choosing a machine and a data type — which is the question the ranking was supposed to help with. A weighted sum does not eliminate the workload dependence; it hides it inside a number somebody picked.

One of the columns is not a cost. Peak auxiliary space is a constraint: below the limit it costs nothing and above it the program does not run. Adding it to a sum with a weight treats a cliff as a slope. The frontier handles it correctly because dominance does not require the axes to be commensurable, which is exactly why a frontier is the right structure here and a weighted sum is not.

The sum destroys the information that survives not knowing the weights. Dominance is weight-independent: if one algorithm beats another on every axis, it beats it under every weighting, and that conclusion is available before any weight is chosen. Summing first and ranking second throws that away and replaces it with an answer that is only as good as the weights.

So the procedure this site uses — report the vector, draw the frontier, discard the dominated — is not an evasion of the choice. It is everything that can be said before the choice, and the choice belongs to whoever knows the data.

With six columns, almost nothing is dominated

The section above says that several algorithms are dominated on every axis at once and can be discarded outright. That is worth actually computing from the table rather than asserting, and the answer is smaller than the sentence suggests.

An algorithm is dominated when some other algorithm ranks at least as well on all six columns and better on one. Working through the ten rows:

Bubble sort is dominated by insertion sort — 8, 1, 9, 8, 8, 1 against 9, 10, 10, 10, 10, 3, better or equal in every column and better in five.

Quicksort with a first-element pivot is dominated by quicksort with median-of-three — 4, 6, 2, 1, 1, 6 against 5, 7, 5, 2, 5, 8, better in all six.

And that is the whole list. Eight of the ten algorithms sit on the six-dimensional frontier, including selection sort, bubble sort’s near-twin by comparison count, which survives because nothing beats its write count; including heapsort, which is beaten on five columns by several algorithms and holds its place on peak space; including Shellsort, which is nobody’s first choice on any column and nobody’s worst on any either.

That is a genuinely deflating result and it is the right one. Adding columns destroys dominance, necessarily and monotonically: every new axis is a new way for an algorithm to be the best at something, so the frontier can only grow as the vocabulary does. With one column there is a total order and a winner. With six there is a frontier of eight, and the discarding step that looked like the useful half of the method removes two rows.

Which sharpens what the method is actually for. It is not a filter that narrows ten to two; it is a filter that removes the two algorithms nobody had to be told about — a quadratic sort beaten by a better quadratic sort, and a pivot rule beaten by a better pivot rule. Everything interesting survives, and it survives because the six quantities genuinely conflict rather than because the measurement is too coarse to separate them.

The consolation is that dominance, where it does hold, is the strongest statement available. Nothing about weights, machines or data types can rescue bubble sort here: it is beaten by insertion sort under every weighting of these six columns simultaneously, which is a conclusion no benchmark on one workload could reach.

And there is a seventh column

Six is not the vocabulary either, and the missing one is worth naming because it would reorder the table again in a direction none of the six anticipate.

A processor guesses which way a branch will go and pays a penalty when it guesses wrong. That penalty is not a function of how many comparisons an algorithm performs; it is a function of how predictable their outcomes are. Two algorithms with identical comparison counts can differ enormously in it — and this collection has measured exactly that, in the essay on the branch the machine guesses.

Quicksort’s partition is the standard bad case. Each element is compared against the pivot and branched on, the outcomes are essentially a coin flip on random data, and no predictor does better than chance. Merge sort’s inner loop branches the same way, and a binary search’s does too — which is why a search with no branch to miss exists at all, doing strictly more work by every count in this table and winning on the column the table does not have.

So the honest statement of this essay’s finding is that the vocabulary has at least seven words, the seventh is independent of the other six, and the frontier is larger still. Every column added so far has made the answer less decisive rather than more, which is what it looks like when a question was never a question about algorithms in the first place.

The rung this sits on

This is the fourth essay in the counting field and it exists because the field’s first three each added a quantity and none of them stepped back.

Counting instead of timing established that an operation count is exact, reproducible and machine-independent where a duration is none of those. Fitting a class established that a complexity class is a claim about counts that can be refused. One run, four counts established that there are four of them and they disagree.

What is new here is the shape of the conclusion. The earlier essays are about getting a number right. This one is about there being no number: the measurement is a vector, the vector’s components genuinely conflict, and collapsing it requires information that lives in the workload rather than in the algorithm.

That is not a counsel of despair and it is not relativism about performance. Every one of these numbers is exact and reproducible, several algorithms are dominated on every axis at once and can be discarded outright, and the frontier between any two columns is a short list. What cannot be done is the last step — turning the short list into a single name — without saying what the data looks like. Every summary that produces a single name has made that step and has usually not noticed.

Peak auxiliary space, random input, n = 4,096The largest number of slots live at once — scratch buffers plus stack frames — for each sort. The axis is logarithmic because the range is: 5 of these hold one slot at their peak, 3 hold a stack of about log₂ n frames, and the rest hold a second copy of the array. All of them are routinely described with the same two words.peak slots held at once, logarithmic18645124096Insertion sort11Selection sort11Bubble sort11Heapsort11Shellsort11Quicksort, median of three22log nQuicksort, random pivot28log nQuicksort, first-element29log nMerge sort with a cutoff4,106nMerge sort4,110nn = 4,096, random inputone slot = one array element or one stack frame
Fig. 7 The column that disagrees most with the classical one. Five algorithms hold a single slot; two hold the whole array. Nothing about a complexity class distinguishes them, and for a large enough n this column decides whether the program runs at all.

What this makes readable

Essays that name this one as a prerequisite.

Named alongside this one

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

What links here

The 8 essays that link to this one and share the most of its objects, of 27 that link here.

The objects this essay names

Each one links to every other essay that touches it.

CacheCost modelDominanceMeasurementMiss ratePareto frontierQuicksortRankingSwapsTrade off