Concept

Merge policy — where it appears

The rule deciding which runs a sort combines and in what order, whose invariants are separate from the merge itself and can be wrong on their own. Its invariants are separate from the merge itself and can be wrong on their own, which is exactly what happened in a shipped library sort for seven years.

Named by 6 essays across 3 fields — each of them below, with the objects they name alongside it.

Timsort95,770shipsIntrosort130,863shipspdqsort114,408shipsDual-pivot116,836shipsMerge sort96,145textbookHeapsort187,796textbookQuicksort, median-3119,098textbookalgorithmcomparisonsrandom, n = 8,192comparisons, counted exactly

The sort the library ships

Every sorting algorithm measured on this site so far has one thing in common — none of them is what runs when a program calls sort. Python, Java, Rust and Android run Timsort; C++ runs introsort; Java's primitive sort is dual-pivot quicksort. Not one of the four was in this collection, and the reason it matters is that they are not algorithms in the sense the other essays use the word.

practice · Practice
10³10⁴10010³n (elements)block transfersM = 256 · fan-in 7M = 1024 · fan-in 31B = 32, M as labelled4 passes against 3

Sorting what will not fit

Merge sort's Θ(n log n) is a statement about comparisons and says nothing about a file larger than memory. Counted in transfers the answer is (n/B)·log_{M/B}(n/B), and the base of that logarithm is the number of blocks that fit in memory — so doubling the memory does not halve the work, it moves a staircase. The measured cost jumps by 32,768 transfers at one step and by nothing for the next four.

applied · Transfer
dark: with galloping · pale: with the mode removednearly sorted33,95221,373 jumped−37.7%few distinct values57,91241,178 jumped−36.9%random95,7702 jumped+0.0%n = 8,192, MIN_GALLOP = 7comparisons, counted exactly

When galloping pays

Timsort's merge does not always take elements one at a time. When one run has won seven times in a row it switches to searching for how many to take at once, and switches back when that stops paying. The mode saves 22,104 comparisons on nearly sorted input, 33,270 on input with few distinct values, and costs exactly six on random input — which is the whole design in three numbers.

practice · Practice
three-entry rule (as shipped, 2002–2015)1414101410314103141054+14+10+3+2+4final stack: 14, 10, 5, 414 is not > 10 + 5 — the invariant is brokenfour-entry rule (Java, after the proof)141410141031410333+14+10+3+2+4final stack: 33every triple satisfies the invariantrun lengths 14, 10, 3, 2, 4both outputs are correctly sorted

The invariant that was wrong for seven years

Timsort's merge policy is supposed to keep its run stack shallow, and the rule that enforces it inspects the top three entries. In 2015 a group of formal-methods researchers proved that the rule does not imply what it was believed to imply. Thirty-three elements are enough to break it, the array still comes out perfectly sorted, and the defect is in a structure that nothing about the output can show.

wrong · Structure
0.020.050.10.20.5121235810filter bits a keywasted reads per absent lookupno filters: all 3 levelssame rate every levelrates sized to levelsn = 65,536, size ratio 4, first run 1,024 keysthe same memory at every point

The filter each run carries

A log-structured store turns every lookup for a missing key into a read of every level, and a Bloom filter on each run buys those reads back with memory. Spread five bits a key evenly across three levels and a missing key still wastes 0.279 reads. Give the small levels more bits and the large one fewer — the same memory — and it wastes 0.201. At four levels the gap is 0.382 against 0.209, because sized filters stop the waste growing with the number of levels.

applied · Transfer
1M2M4M16M64Mmean run length, in memoriesrandom33 runssorted1 runreversed64 runssorted, 1% arriving late2 runssorted, 10% arriving late7 runs262,144 records, 4,096 in memorydashed: two memories

Runs twice as long as memory

Feed 262,144 random records through a heap that holds 4,096 and the sorted runs that come out average 1.94 memories — the snowplow's famous factor of two. At a fan-in of 63 that saves a merge pass at 262,144 records, and at two of fourteen sizes in all. Feed the same heap a sorted file with one record in a thousand out of place and it writes two runs instead of sixty-four. And it spends 19 comparisons a record doing so, on every input, where sorting the chunks spends five on sorted data. The factor of two is the least of what the method does.

applied · Transfer

Named alongside it

The objects these essays reach for when they reach for this one.

Block transferTimsortAdaptive sortComparison countComplexity classExternal-memory modelExternal merge sortFan-inIntrosortJavaRegimeStability

All concepts