The collection

Every essay — page 3

Page 3 of 18, continuing through the fields in the same order.

What a bound is Counting The floors What the machine does Structures Two parameters The other axis When the algorithm flips a coin What the libraries do When it does not fit One pass, and no room The data that is not a number When the algorithm is a table The index that replaces the text What is taught wrongly Ladders Objects Search

What a bound is

Asymptotic notation is a statement about a limit, not a measurement. Knowing precisely what it claims is the difference between using it and being misled by it.

50%30%10%3%1%0.050.10.20.512share of queries outside the top 2% of rankscost over parts balanced by keysbalanced by past queriessize × queries equal(∫√q)², predictedseven parts, fifty past queriesdotted: parts balanced by keys

Parts cut where the questions are

A file of four million keys, written once as seven parts so that each later selection reads only its own part, can be cut where the keys are or where the questions are. When nine queries in ten ask about the top 2% of ranks, parts balanced by keys cost 1,170 block transfers a query. Parts that each serve an equal share of past queries cost about as much, and a little calculus says they must, on average. Parts cut so that each one's size times its share of queries is equal cost 290, which is the optimum's square-root law, measured to within a third at every concentration tried. The saving lasts exactly as long as the questions stay where they were counted: moved a percentile and a half, the fitted parts cost more than the plain ones.

5 figures
0.020.050.10.20.530050010³10³drift, percentiles every 100 queriestransfers a querya band that never moves, 356best fixed interval, chosenafterevery 1,000 queriesthe law, half-life 250the law, half-life 1,000four streams of 20,000 queries a speedrewrites included

A recut the law decides

Parts of a file cut by the square-root law where selection queries fall lost their advantage once the queries moved a percentile and a half. A partition that recuts itself when the law says the saving pays for the rewrite was predicted to stay within 20% of the best fixed recut interval at every drift speed, and to fail on traffic that jumps. With recent queries decayed at a half-life of 250, it stays within 7% up to a drift of 0.1 percentiles every hundred queries and within 22% at five times that. On jumping traffic it beats every interval fixed in advance, rewriting 13 times a stream where the best interval rewrites 80. The law does not remove a setting: it trades the interval, which has to move fourfold with the speed, for a half-life that does not.

6 figures
0%25%50%75%100%05,00010,00015,00020,000queriesrank in the filethe band's centrethe six cutsone stream, drift 0.1a cut is drawn at each recut

Every cut the law moves

A file cut into seven parts by the square-root law, and recut when the law says the saving pays, was charged a full rewrite for every recut. Charging only the parts whose boundaries change was predicted to cut a recut's cost to a tenth on slow drift. It cuts it by 2%. When the band of hot queries moves, the law moves 98% of its cuts, a median of nineteen thousand ranks each, because the parts on either side of the band have to share out what the band left. Holding small moves back keeps a quarter of the file and costs more a query; recutting only the parts around the band costs up to three times as much.

5 figures
the bandthe law, 7 partsthe band's part: 80169412601694169416907 by keys + band in 3 (9 parts)the band's part: 116117011706301172117011701170the law, 9 partsthe band's part: 681402140210241402140212526 by keys + band in 4 (9 parts)the band's part: 72136613649821366136413660%25%50%75%100%rank in the file; numbers are transfers a query in that part2^22 keys, blocks of 1,024a still band 2% wide

The two parts the law was not given

The square-root law moves almost every cut of a seven-part file whenever its band of hot queries moves. A background cut once by keys, with only the part holding the band cut again, was proposed instead: a recut would rewrite a seventh of the file. It does, and it beats the law at every drift speed — 365 transfers a query against 417 at a moderate drift — but it wins with nine parts to the law's seven. Given nine, the law costs 322. Held to seven, the fixed background loses to the law at every speed. What survives the fair comparison is narrower and still useful: six parts by keys, with the band's part cut in four, ties the nine-part law on drift for a quarter of its rewriting. It loses on jumps, because a fixed background waits about three hundred queries to believe the band has left before it moves, and it pays double wherever a background cut runs through the band.

7 figures
300400queries between jumps of the band, on averagetransfers a query, writes included1,0002,5005,00010,0006 by keys, majority's part in 46 by keys, paying part in 46 by keys, two parts splitthe law, 9 partsa half-life of 250, changed parts chargeda band that jumps

A background that holds two bands

A file kept in six fixed parts by keys, with the part holding the band of hot queries cut in four, ties the nine-part square-root law on drifting traffic and loses to it by 10% to 16% when the band jumps, because after a jump it pays for a whole background part for three hundred queries before moving its split. Asking every part whether a split would pay, instead of asking which part holds the most queries, was proposed to end the wait. It changes nothing: 363 transfers a query against 365. What ends the wait is the second half of the proposal, letting two parts be split at once in three and two pieces: on jumps the background comes within 2% to 4% of the law, and on fast drift, which nobody expected it to touch, it beats the law by 6% to 10%, because a band lying across a background cut is held by two split parts rather than by one split and one whole.

7 figures
2,0484,0968,1921010010³10⁴nleast fast memory, cellsheapsortShellsortquicksort, random pivotquicksort, median of threefamily, 16 cellsfamily, 64 cellsfamily, 256 cellswithin 8 × n²/Bdoubling grid of memories

The memory a product does not charge

Time times space charges a sort for the cells it writes and not for the fast memory it needs to write them cheaply. Counted three ways, on random keys in lines of eight, a read-only family stays within a factor of eight of the floor with 32 to 256 cells of fast memory at every length, while heapsort needs all of n and quicksort an eighth of it. Quicksort's need falls far faster than predicted as the factor loosens. And the product the floor leaves out, transfers times fast memory, separates the two kinds of sort completely: no read-only sort can push it far below n²/B, and quicksort reaches a twentieth of it.

6 figures
1664256102440961210100fast memory M, cellstransfers × writable cells, per n²/Btwo: one read, one write of nheapsortShellsortquicksort, random pivotfamily, 16 cellsfamily, 64 cellsfamily, 256 cellsn = 8,192, write-backs chargedthe floor at one

The lines a sort writes back

A replay that charges a transfer for every line brought into fast memory and none for a line sent back flatters every sort that writes in place. Charged for its write-backs, quicksort's transfers rise by 1.9 at every memory short of n, as predicted, and heapsort's by 1.8, not the predicted three, because no line can go back more often than it came in. The cost that changes the account is the last one: at n itself every in-place sort pays two reads of its array, so none of them can come within a factor of two of the floor, while the read-only family reaches 1.07.

7 figures
fast memory, cells (log scale)transfers × fast memory, in n²/B (log scale)0.5124816321632641282565121,0242,0484,0968,192sorted buffer, with write-backsheap buffer, with write-backssorted buffer, reads alonebuffer of 64logarithmic scales

A buffer that does not shift its tail

The read-only family of sorts scans its input once for each batch of the smallest values not yet output, keeping them in a buffer; charged for the dirty lines it sends back to slow memory, its least transfers × fast memory rose from 1.29 of n²/B to 1.87, because a sorted buffer takes each value by shifting every larger entry one place. Kept as a max-heap instead, the buffer was predicted to write back about a third of the lines it reads below its own size and to bring the product back towards 1.29. At a buffer of 64 cells it writes back 33% at its best memory, against 45%, and its least product falls to 1.71: right about the share, wrong about the product, since a third written back is still a third more transfers. At 256 cells the heap does more than predicted — 1.47 million accesses against 6.94 million, a third of the misses — and its least product, 1.62, is under any buffer's on the earlier charge. At 16 cells it does nothing.

5 figures

Counting

Comparisons, swaps, reads and writes are exactly countable and machine-independent. Every claim on this site starts by counting rather than by reading the code.

after 0 writes0 cmpafter 32 writes32 cmpafter 64 writes63 cmpafter 95 writes94 cmpafter 127 writes125 cmpafter 159 writes157 cmprandom input, seed stated in lib/count.js157 comparisons in this run

Counting instead of timing

A stopwatch measures the laptop it runs on. A counter measures the algorithm. Every number on this site comes from an array that increments a tally each time it is read, written, compared or swapped — which makes the counts exact, reproducible to the last digit, and identical on every machine that has ever built this page.

6 figures
10010³10010³10⁴10⁵10⁶ncomparisonsInsertionMergeHeapsortQuicksorta power law is a straight line herecomparisons, counted exactly

Fitting a class to measurements

A complexity class is normally read off the shape of the loops and written down. Here it is fitted to counts taken across three orders of magnitude, and an algorithm is granted a class only if the fit holds — which turns a statement about code into a statement that can fail.

7 figures
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

One run, four counts, four answers

The question “how many operations” has no answer until the operation is named. Selection sort makes more comparisons than any other algorithm here and fewer writes than almost all of them; bubble sort matches its comparisons and does 124 times the swapping. The ranking depends entirely on which count is chosen, and the choice needs justifying.

6 figures
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

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.

7 figures
10³10⁴10³10⁴10⁵nrandom bitsskip list, one build — ntreap, one build — nreservoir, Algorithm R — n log nn from 256 to 16,384bits charged including rejections

Counting the coin flips

A skip list spends 2.03 random bits per key and a treap spends exactly 32. Reservoir sampling spends 1,356,399 bits on a stream of 65,536 and a better version spends 9,380. None of those numbers appears in any complexity class any of these structures is described by, and none of the site's other three counters can see them.

6 figures
Dual-pivot116,836shipsIntrosort130,863shipspdqsort114,408shipsTimsort95,770shipsalgorithmcomparisonsrandom, n = 8,192comparisons, counted exactly

Two pivots and what they cost

Java changed its primitive sort in 2011 on the strength of an analysis showing dual-pivot quicksort does fewer comparisons than the classical one. It does. It also does nearly twice the swaps, and the analysis that decided the matter counted neither — it counted a weighted combination that had to be chosen before any conclusion could be drawn.

7 figures
4 bytes32 bytes128 bytes512 bytesrecord:QuicksortMerge sortShellsortHeapsortInsertion sortSelection sortBubble sortQuicksortShellsortMerge sortHeapsortSelection sortInsertion sortBubble sortQuicksortShellsortMerge sortSelection sortHeapsortInsertion sortBubble sortSelection sortQuicksortShellsortMerge sortHeapsortInsertion sortBubble sort1234567n = 512, random input, key 8 bytesa read or a write moves the record; a comparison touches the key

The exchange rate nobody wrote down

Three earlier essays have said in passing that the ranking would change if the elements were large records. None of them computed it. Computed, selection sort goes from second-worst of seven at four bytes a record to best of seven at five hundred and twelve — and the crossover against each rival is a division that takes one line.

6 figures
the language's sortMerge sortlog₂(n!)random43,810 / 43,976nearly sorted10,806 / 24,845already sorted4,095 / 24,576reversed4,095 / 24,576few distinct values27,561 / 42,338n = 4,096, counted through the comparatorthe first column is a fact about the engine, not about sorting

The count that came from somewhere else

Every count in these essays is described as exact, reproducible, and identical on every machine. Two of them are not. A spanning-tree measurement quotes 42,385 comparisons spent in a sort, and that sort is the language's own — whose comparison count is specified nowhere, varies by a factor of thirteen across input kinds, and comes within one per cent of the information floor on random input.

6 figures
0.0000.0100.0200.030150200comparisonsprobability per comparison countmean 139.86exact400 runsn = 32, pivot chosen uniformlysampled mean 140.19

A distribution computed rather than sampled

Quicksort with a random pivot makes a random number of comparisons, and every plate in these essays reports one draw from that distribution. The distribution itself can be computed exactly — every pivot, every split, weighed — and on sixty-four elements its mean is 360.706 and its standard deviation 36.741. A hundred real runs average 365.98; two thousand average 360.74. The exact answer says how many runs a sample needs, and it is fewer as the input grows.

6 figures
every orderthe named inputsmean0102030comparisonsInsertion sort7 to 28 · named inputs reach 28Merge sort12 to 17 · named inputs reach 16Heapsort21 to 29 · named inputs reach 27Quicksort, first-element pivot13 to 28 · named inputs reach 28Quicksort, median of three25 to 29 · named inputs reach 2540,320 orders of 8 distinct elements3 worst cases unnamed

A count over every input

Run five sorts on every one of the 40,320 orderings of eight elements and read off each one's best, mean and worst comparison count. Then mark where the inputs a benchmark generator names — sorted, reversed, nearly sorted, random, few unique — land. For merge sort, heapsort and quicksort with a median-of-three pivot, the worst case is an ordering none of them produces, and for the last of the three every named input lands on its best case.

6 figures
40%60%80%100%8121624324864elements sortedshare of the known worst case the climbs reach, on averageInsertion sort · 20 of 24Merge sort · 24 of 24First-element quicksort · 0 of 24hollow: none reached it24 climbs a size · 100 swaps per elementworst cases known exactly

The worst case found by climbing

A search that swaps two elements at a time and keeps whatever does not lower the count finds the worst case of all five sorts at eight elements, where every answer can be checked. At sixty-four it finds merge sort's worst case every time and reaches 39% of first-element quicksort's — whose worst case is sorted input, the most famous bad input there is. Checking a search where the answer is known certifies it only there.

7 figures
0.0010.010.11125102050fraction of positions reshuffled, pmean comparisons, in multiples of the mean on random inputFirst-element quicksort, 81.9×Median-of-three quicksort, 41.4×Insertion sort, 2.0×Merge sort, 1.0××: unshuffled2,048 elements · 12 shuffles a point1 = the mean on random input

A worst case ten positions wide

Sorted input costs first-element quicksort 2,096,128 comparisons on 2,048 elements, 82 times its average. Reshuffle about eleven of the 2,048 positions and the cost halves — and it takes about ten at 128 elements, and between ten and thirteen at every size between. Reversed input costs insertion sort twice its average, and reshuffling half the positions still leaves 71% of the work. A worst case is a place in the space of inputs, and the two famous ones are places of very different sizes.

7 figures
0102030comparisons on one orderingfloor 16Insertion sort7 to 28 · mean 19.28Merge sort12 to 17 · mean 15.73Heapsort21 to 29 · mean 25.81First-element quicksort13 to 28 · mean 16.92Median-of-three quicksort25 to 29 · mean 26.30Batcher's network19 on all 40,32040,320 orders of 8, enumeratedfloor ⌈log₂ 8!⌉ = 16

The sort whose count has no distribution

Batcher's network makes nineteen comparisons on every one of the 40,320 orderings of eight elements — sorted, reversed, adversarial or random — because it decides which pairs to compare before it sees any of them. That is two above merge sort's worst case and four below heapsort's best. At 65,536 elements the same refusal to adapt costs 4.07 times merge sort's worst case, and it buys three things no adaptive sort has, one of which is a proof of correctness that takes 65,536 inputs instead of twenty trillion.

5 figures
1101001,00010,00010⁵10⁶k, the elements the caller readscomparisonssort all, read kbuild a heap, pop kselect k, sort thoseincremental quicksortkeep the best k while scanningknockout tournamentdashed: the floorlabels at k = 1,00065,536 random distinct keysevery answer checked

The count of the part that was read

Handing back the smallest ten of 65,536 keys in order costs 965,656 comparisons by sorting them and 65,670 by a knockout tournament, against a floor of 65,526. Read to the last element, the same tournament makes exactly merge sort's 965,656 — it is merge sort, charged one element at a time. A sort's count has no term for how much of its answer anyone reads, and the two floors that do have one cannot simply be added.

5 figures
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

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.

6 figures
reads and writescache missesRadix sort, 8-bit digitsno comparisonsMerge sort965,752 comparisonsHeapsort1,895,405 comparisonsQuicksort, median-of-three1,187,435 comparisons65,536 keys of 32 bitsdark: the sort that compares nothing

The sort that makes none of them

Every count on this collection is a count of comparisons, swaps, reads or writes, and radix sort makes zero of the first. On 65,536 keys it moves five times less data than merge sort, misses the cache three times more, and sits 954,037 comparisons under the floor no comparison sort can go beneath — which is not an achievement, because the floor was never a statement about it.

6 figures
1416642561k2k4k8k16k33k66k11010010³10⁴10⁵10⁶keys handed backcomparisons no method can go underthe losers, and the outputs' ownorderevery element not handed back losesthe answer is one of n!/(n−k)!sequences65,536 keyseach is a claim about every possible method

Two floors that can be added

Handing back the ten smallest of 65,536 keys has two floors under it and neither is close where they cross — the larger of the two is 63,821 comparisons at k = 4,000 and the best method makes 125,401. They can be added, because a comparison that eliminates a key the caller never sees can never be a comparison that orders two the caller does see. Charged together the floor rises 62%, and the tournament goes from 1.97 times it to 1.21.

6 figures
0%25%50%75%Merge sort0.0% · 1.03× the floorQuicksort, random pivot0.0% · 1.16× the floorQuicksort, first-element pivot0.0% · 1.18× the floorMerge sort with an insertion cutoff0.0% · 1.31× the floorInsertion sort0.0% · 9.83× the floorShellsort9.8% · 1.31× the floorQuicksort, median-of-three15.4% · 1.18× the floorHeapsort26.2% · 1.45× the floorBubble sort49.2% · 9.83× the floorSelection sort59.9% · 7.77× the floorshare of comparisons whose answer was already implied256 elements, random orderlight: repeats nothing

The questions a sort asks twice

Selection sort makes 32,640 comparisons on 256 elements and 19,561 of them have answers it already holds. Remove every one and it still makes 7.8 times the information floor, because a question can be new and nearly worthless: insertion sort repeats nothing at all and removes 0.72 of a bit per comparison where merge sort removes 0.96. And bubble sort, less its repeats, makes exactly insertion sort's comparisons — at every size.

6 figures
32641282565121010010³10⁴elements sortedword operations a comparison must cost to break evenSelection sort, pairsSelection sort, closureBubble sort, pairsBubble sort, closureHeapsort, pairsHeapsort, closureQuicksort, median-of-three, pairsQuicksort, median-of-three, closuresolid: pairs asked · dashed: everything impliedrandom input · counted exactly

The price of remembering an answer

Give selection sort a table of the pairs it has already compared and it makes 16,805 comparisons on 256 elements instead of 32,640. The table pays for itself once a comparison costs more than 3.1 word operations, and that price stays near three at every size measured. Give it the full closure of everything its answers imply and it makes 13,079. The closure pays only past 425 operations a comparison at that size, and past 1,659 at twice the size. The cheap memory is worth having on long keys. The complete one is worth having almost nowhere.

5 figures
11010010³10⁴10⁵10⁶k, the number handed backcomparisonstournament, measuredfloor that names the outputsbest earlier floorn = 65,536, one shuffled inputthe floors are worst-case, the count is one input

The comparisons that name the answer

Returning the 4,000 smallest of 65,536 keys in order needs 61,536 comparisons to eliminate the rest and 42,100 to order the ones returned. That was the floor, 103,636, and a tournament made 125,388. What the floor never charged is saying which 4,000 come back. Charge that, and the floor is 125,341. On the same input the tournament is 47 comparisons above it, and for every k up to a hundred it is exactly on it.

5 figures
1001000entries in the cacheshare of the repeats it catches0%25%50%75%100%256 entries, one a pair asked in a passSelection sortBubble sortdashed: collisions alonerule at 100%: the table ofevery pair256 random elements · direct-mapped, one pair a slotoccupancy, not distance

The record that forgets on purpose

A table of every pair selection sort might ask is 131,072 bits at 256 elements and catches every repeated question. A direct-mapped cache of 1,024 pairs is 16,384 bits and catches 91% of them, at a break-even of 3.53 word operations a comparison against the table's 3.12. What decides the cache is not how long the sort waits before asking again — 98% of its repeats come within one pass — but how many other questions it asks meanwhile, and a model with nothing in it but collisions predicts every measured share to within 2.6%.

6 figures
00.0250.0500.0750.100200300400500comparisonsprobability of exactly that manymerge sort's worst case, 321merge sort, sd 4.22quicksort, sd 36.74n = 64, both exactratio of spreads 8.7

The spread a merge sort does not have

On 65,536 random keys quicksort's comparison count has a standard deviation of 42,470 and merge sort's has one of 141.9 — three hundred times smaller, on means only a quarter apart. Merge sort's count is a fixed bill minus 65,535 independent leftovers, one per merge, each worth about a coin toss, so its spread grows as the square root of n where quicksort's grows as n. From 256 keys up a single run of merge sort pins its mean to one per cent, and a third of quicksort's whole spread is decided by its first pivot.

9 figures