What the libraries do

Pieces that fit the buffer

A sorted file with a late tenth appended costs the buffer-limited stable merge sort 2.93 moves a key at a buffer of a 64th, fifteen times the least any sort could move, and two thirds of it is the tail sorting itself. Taking the tail a buffer at a time, inserting each key into a sorted piece in the buffer and merging the piece into the head, was predicted to bring that under two moves a key at small buffers. It never comes within twice the merge sort: insertion costs a quarter of a piece a key, and the smaller the pieces the more often each one's merge crosses the head's top fifth. The same pieces sorted in place by merging do pay, at 2.41 moves a key, and for a reason the proposal did not name: a piece that fits the buffer merges into the head without a single rotation. The gain lasts only while the tail belongs near the top of the file.

The moves no stable sort can skip set the buffer-limited stable merge sort against a floor: a key that starts out of place must be written at least once, and with one spare cell each cycle of the permutation costs one more write. On a sorted file of 65,536 keys with a late tenth appended, keys that belong in the top fifth of the file but arrive at its end in random order, the floor is 0.20 moves a key. The sort, with a buffer of a 64th of the file and the trim the skip the split already made gave it, moves 2.93. Two thirds of that is the late tenth sorting itself: a dozen levels of merging over 6,554 keys that arrived together and in no order.

That essay’s closing section proposed handling a short final stretch differently. The keys arrive together at the end, and at a 64th the buffer holds 1,024 of them. Take the tail a buffer at a time. Insert each key into a sorted piece kept in the buffer, then merge the buffered piece into the head, the long sorted run in front of it, and do the same with the next buffer’s worth. The prediction was that insertion into the buffer costs about half a buffer’s length in moves a key, far more than the dozen levels of merging at a 64th, so the rule pays only when the buffer is small. There, the late tenth should fall from 2.93 moves a key to under two. It named the failure: six pieces of a thousand keys, merged into the head one at a time, each cross the head’s top fifth, and six such crossings may cost more than the tail’s own sort saved.

The rule, and a second way of sorting a piece

The sort finds the head with one pass, the longest sorted prefix. That pass is the one a run is a property of the input argued every adaptive sort should make, since the runs it finds are the input’s own measure of how sorted it already is. Here it finds one run of 58,982 keys and then a tail of 6,554 in no order, and the rule applies only to an input of that shape: a long head and a final stretch no longer than a tenth of the file. The tail behind it is taken in pieces of the buffer’s length. Every piece’s keys are written into the buffer, sorted there, and merged into the head from the right: the merge compares the largest key of the head with the largest of the piece and writes the larger to the free end, so every head key larger than the piece’s smallest moves up once, and every piece key is written once. The head then includes the piece, and the next piece follows.

Two ways of sorting a piece are measured. By insertion, as proposed: each key is placed in the sorted piece by binary search, and every key after its place shifts one step. By merging: the piece is sorted where it lies by the same buffered merge sort that sorts the whole file, then copied into the buffer. The second was not in the proposal. It was added once the first was measured, to separate what insertion costs from what taking the tail in pieces costs.

Moves are counted as the merge sort counts them, one for every key written, and every result is checked sorted. The keys are distinct, so stability is not at issue.

Insertion never comes close

On a sorted file with a late tenth appended, sorting the tail a buffer at a time by insertion costs 25.96 moves a key at a 64th against the merge sort's 2.93, and never comes within three times it; the same pieces sorted by merging cost 2.41 there, and lose once the buffer is a 256thMoves a key of a 65,536-key file whose last tenth arrives in random order from the top fifth of the keys, against the buffer. merge sort, whole tail: 1/4 1.89, 1/16 2.04, 1/64 2.93, 1/256 4.39, 1/1024 6.45, 1/4096 8.82. pieces by insertion: 1/4 164.92, 1/16 87.79, 1/64 25.96, 1/256 10.40, 1/1024 16.96, 1/4096 58.46. pieces by merging: 1/4 1.89, 1/16 1.91, 1/64 2.41, 1/256 4.96, 1/1024 16.01, 1/4096 58.45. Logarithmic in moves.125102050100buffer, as a share of the filemoves a key of the file (log scale)1/41/161/641/2561/10241/4096pieces by insertionpieces by mergingmerge sort, whole tail65,536 keys, a late tenth from the top fifthmoves counted, sorted checked
Fig. 1 Moves a key of the file against the buffer, on the late tenth. The merge sort: 1.89 at a quarter, 2.04 at a 16th, 2.93 at a 64th, 4.39 at a 256th, 6.45 at a 1,024th, 8.82 at a 4,096th. Pieces by insertion: 164.9, 87.8, 26.0, 10.4, 17.0, 58.5. Pieces by merging: 1.89, 1.91, 2.41, 4.96, 16.0, 58.5.

At a buffer of a 64th, sorting the late tenth a buffer at a time by insertion costs 25.96 moves a key of the file, nine times the merge sort’s 2.93; at its best, at a 256th, it costs 10.40 against 4.39. It never comes within twice the merge sort at any buffer measured. The proposal expected insertion to lose at large buffers and win at small ones. It loses at large buffers by the margin expected and at small ones by more, because at small buffers the pieces’ merges into the head grow faster than insertion’s cost falls.

The same pieces sorted by merging cost 2.41 moves a key at a 64th, 18% under the merge sort, and 1.91 at a 16th, 6% under. Below a 64th they lose, and at a 1,024th they cost 16.0, two and a half times the merge sort. At a quarter of the file the tail is one piece and the rule is the merge sort exactly, 1.89 both ways. The proposal’s target of under two moves a key is met only at a 16th and above, where the merge sort was already at 2.04; at the 64th it was aimed at, the best the pieces reach is 2.41.

So the proposal’s question has two answers. Insertion is the wrong way to sort a piece at any buffer measured. Taking the tail in pieces that fit the buffer is right at a 64th and a 16th, and wrong below.

A quarter of a piece, every key

Inserting a tail key into a sorted piece in the buffer costs a quarter of the piece's length, not half: 248.35 moves a key for pieces of 1,024, against 256 and 512, because a key shifts half of a piece that is on average half fullMoves into the buffer for each key of the late tenth — the copy in and every key shifted to make room — when the tail is taken in pieces of 16 to 1,024 keys sorted by binary insertion. 1,024: 248.35, 512: 125.82, 256: 64.48, 128: 32.70, 64: 16.72, 32: 8.78, 16: 4.77. A quarter of the piece: 256, 128, 64, 32, 16, 8, 4.141664256512piece length, keys (each piece fills the buffer once)moves a tail key, into the buffer (log scale)1,024512256128643216half the piece, predicteda quarter of the piecemeasuredthe late tenth, 6,554 keysbinary insertion into the buffer
Fig. 2 Moves into the buffer for each key of the late tenth, the copy in and every key shifted to make room, against the piece’s length. 248.35 for pieces of 1,024, 125.82 for 512, 64.48 for 256, down to 4.77 for 16. A quarter of the piece: 256, 128, 64 and 4. Half of it: 512 and 8.

Inserting a tail key into a sorted piece costs a quarter of the piece’s length in moves, 248 for pieces of 1,024, not the half the proposal expected. The prediction counted a key shifting half of a full piece. A piece fills from empty, so on average it is half full when a key arrives, and a key lands on average in its middle and shifts half of what is there. A quarter is half of a half.

The comparisons are not the problem. Binary insertion finds each key’s place in about ten comparisons for a piece of 1,024, fewer than a merge sort spends on the same key. Every cost here is a write, and the count is of writes because a buffer-limited sort is a sort that is short of room to write into, which is the reason this line of pages counts moves rather than time, the choice counting instead of timing made for the whole collection.

The difference between a quarter and a half is a factor of two, and it does not change the verdict. At a 64th, a quarter of 1,024 is 256 moves for each of 6,554 keys, 24.8 moves a key of the file before any piece reaches the head. The merge sort’s whole cost is 2.93. Insertion’s cost per key grows with the piece and a merge level’s does not, which is the old trade where insertion sort actually wins priced for small arrays, and at a thousand keys it is a long way past its crossing.

Crossing the head, once a piece

Each piece's merge into the head crosses the head's top fifth again, so the head's share grows as pieces shrink — 1.13 moves a key with 7 pieces of 1,024, 57.99 with 410 of 16 — while insertion's share falls from 24.83 to 0.48; the two never meet below 10.40On the late tenth, moves a key of the file into the buffer (by insertion, and by sorting the piece in place and copying it in) and into the head, for pieces of 1,024 down to 16 keys. 1,024 (7 pieces): insertion 24.83, merging 1.29, head 1.13. 512 (13 pieces): insertion 12.58, merging 1.15, head 2.00. 256 (26 pieces): insertion 6.45, merging 1.00, head 3.95. 128 (52 pieces): insertion 3.27, merging 0.86, head 7.82. 64 (103 pieces): insertion 1.67, merging 0.72, head 15.29. 32 (205 pieces): insertion 0.88, merging 0.59, head 29.77. 16 (410 pieces): insertion 0.48, merging 0.46, head 57.99.0.5125102050piece length, keysmoves a key of the file (log scale)1,024512256128643216insertion: into the buffermerging: into the buffereither: into the headthe late tenthpieces of 1,024 = a 64th of the file
Fig. 3 On the late tenth, moves a key of the file into the buffer and into the head, against the piece’s length. Pieces of 1,024 (7 pieces): insertion 24.83, merging 1.29, head 1.13. Pieces of 256 (26): 6.45, 1.00, 3.95. Pieces of 64 (103): 1.67, 0.72, 15.29. Pieces of 16 (410): 0.48, 0.46, 57.99.

Every piece’s merge into the head moves the head’s keys above the piece’s smallest key, and on the late tenth that is most of the head’s top fifth every time: seven pieces of 1,024 cost the head 1.13 moves a key of the file, and 410 pieces of 16 cost it 58. The proposal named this failure and worried about six crossings. Six are affordable. Halving the piece doubles the crossings, and the cost into the head doubles with them.

The arithmetic is simple. The late tenth is drawn from the top fifth of the keys, so the head holds about a tenth of the file in that range, 6,550 keys. A piece of random keys from the range has its smallest key near the bottom of it, so nearly all of those head keys move on every piece, and so do the pieces already merged above them. One crossing is worth about 0.15 moves a key of the file, and PP pieces cost about 0.15P0.15P.

Insertion’s cost into the buffer falls as the pieces shrink, a quarter of a piece a key, and the head’s cost rises. The two lines cross between pieces of 128 and 256, and their sum never falls below 10.40 moves a key. There is no piece length at which insertion pays.

Sorting each piece by merging costs between 0.46 and 1.29 moves a key into the buffer at every piece length, the merge sort’s own cost on a small array plus the copy. It is cheap at every length. What decides the rule’s total is the head, and the head wants few pieces.

What a piece that fits the buffer saves

At a 64th the merge sort spends 1.04 moves a key rotating, because the sorted tail is too long for the buffer when it meets the head; seven pieces that each fit the buffer rotate nothing and cross the head seven times for 1.13, 0.52 a key less in allMoves a key of the file on the late tenth, by where they go. merge sort, 1/16: insertion 0.23, merging 1.66, rotation 0.15; 2.04 in all. pieces by merging, 1/16: the pieces' sort and copy 1.55, into the head 0.36; 1.91 in all. merge sort, 1/64: insertion 0.23, merging 1.66, rotation 1.04; 2.93 in all. pieces by merging, 1/64: the pieces' sort and copy 1.29, into the head 1.13; 2.41 in all.runs extended by insertionmerging with the bufferrotationthe pieces' own sortcrossing the head0123moves a key of the filemerge sort, 1/162.04pieces by merging, 1/161.91merge sort, 1/642.93pieces by merging, 1/642.41the late tenth65,536 keys
Fig. 4 Moves a key of the file on the late tenth, by where they go. At a 16th: the merge sort spends 0.23 extending runs by insertion, 1.66 merging with the buffer and 0.15 rotating, 2.04 in all; pieces by merging spend 1.55 sorting and copying the pieces and 0.36 crossing the head, 1.91. At a 64th: 0.23, 1.66 and 1.04, 2.93; against 1.29 and 1.13, 2.41.

At a 64th the merge sort spends 1.04 moves a key rotating, and seven pieces that each fit the buffer rotate nothing; they spend 1.13 crossing the head instead, and come out 0.52 moves a key ahead. That is where the rule’s gain comes from, and it is not the gain the proposal described.

The merge sort sorts the whole tail first, 6,554 keys in a dozen levels, and then merges the sorted tail with the head. At a 64th that last merge cannot fit: its shorter run, the tail, is six times the buffer. So the library’s merge splits it by binary search and rotation until the pieces fit, the mechanism the buffer a schedule cannot bend traced, and the rotations move each key several times. The pieces never need that. Each one is the buffer’s length, so its merge with the head is a plain buffered merge, one write a key.

The proposal expected the rule to win by replacing the tail’s own sort with merges into the head. It does not replace the tail’s sort: the pieces sorted by merging still cost 1.29 moves a key, against the merge sort’s 1.89 for extending runs and for all of its buffered merging, the final merge with the head included. What it replaces is the rotation. At a 16th the tail is only one and a half buffers long, the merge sort’s rotation costs 0.15, and the rule saves only 0.13.

How far down the tail reaches

Merged pieces pay only while the tail belongs near the top: at a 64th they save 0.25 moves a key when the tail is drawn from the top tenth and 0.52 from the top fifth, tie at two fifths, and cost 1.48 more when it is drawn from the whole fileOn a file whose last tenth is drawn at random from the top 10%, 20%, 40%, 70% or all of its keys, at a buffer of a 64th: moves a key for the merge sort and for pieces sorted by merging, and the merge sort's rotation and the pieces' crossings of the head. 10%: merge sort 1.97 (rotation 0.29), pieces 1.72 (head 0.43). 20%: merge sort 2.93 (rotation 1.04), pieces 2.41 (head 1.13). 40%: merge sort 3.83 (rotation 1.74), pieces 3.81 (head 2.52). 70%: merge sort 5.19 (rotation 2.80), pieces 5.91 (head 4.62). 100%: merge sort 6.53 (rotation 3.85), pieces 8.01 (head 6.72).02468how far down the file the tail's keys belongmoves a key of the file10%20%40%70%100%pieces by mergingmerge sort, whole taila tenth appended, buffer a 64th65,536 keys
Fig. 5 At a 64th, moves a key of the file when the appended tenth is drawn from the top 10%, 20%, 40%, 70% or all of the keys. The merge sort: 1.97, 2.93, 3.83, 5.19, 6.53, of which rotation 0.29, 1.04, 1.74, 2.80, 3.85. Pieces by merging: 1.72, 2.41, 3.81, 5.91, 8.01, of which crossing the head 0.43, 1.13, 2.52, 4.62, 6.72.

The pieces pay while the tail belongs near the top: 0.25 moves a key saved when it is drawn from the top tenth, 0.52 from the top fifth, two hundredths from the top two fifths, and they cost 1.48 more when it is drawn from the whole file. Both of the costs being traded grow with the reach. The merge sort’s rotation grows because the final merge is spread over more of the head; the pieces’ crossings grow because each crossing moves more of the head. The crossings grow faster, seven of them against one rotation. From a tail drawn from the top tenth to one drawn from the whole file, the merge sort’s rotation grows thirteenfold, from 0.29 moves a key to 3.85, and the pieces’ crossings grow sixteenfold, from 0.43 to 6.72. A rotation’s cost is set by how far the final merge’s split points move keys, and a crossing’s by how many head keys lie above a piece’s smallest key, which on a tail reaching the whole file is nearly all of them, seven times over.

The two lines cross at about two fifths. Below that, a tail that fits seven buffers is better merged in seven pieces; above it, the whole tail is better sorted first and merged once, rotations and all. The crossing depends on the number of pieces as well as the reach, which is the subject of the closing section.

A tail that belongs anywhere

When the appended tenth belongs anywhere in the file, the tail rule loses at every buffer below a quarter: pieces by merging cost 8.01 moves a key at a 64th against the merge sort's 6.54, because each piece's merge crosses the whole headMoves a key of a 65,536-key file whose last tenth arrives in random order from all of its keys, against the buffer. merge sort, whole tail: 1/4 2.69, 1/16 3.24, 1/64 6.54, 1/256 10.41, 1/1024 14.92, 1/4096 19.91. pieces by insertion: 1/4 164.82, 1/16 88.36, 1/64 31.92, 1/256 31.10, 1/1024 97.89, 1/4096 367.08. pieces by merging: 1/4 2.69, 1/16 3.51, 1/64 8.01, 1/256 25.67, 1/1024 96.93, 1/4096 367.06. Logarithmic in moves.125102050100200buffer, as a share of the filemoves a key of the file (log scale)1/41/161/641/2561/10241/4096pieces by insertionpieces by mergingmerge sort, whole tail65,536 keys, a random tenth appendedmoves counted, sorted checked
Fig. 6 Moves a key of the file against the buffer when the appended tenth is drawn from all of the keys. The merge sort: 2.69, 3.24, 6.54, 10.41, 14.92, 19.91 at a quarter to a 4,096th. Pieces by insertion: 164.8, 88.4, 31.9, 31.1, 97.9, 367.1. Pieces by merging: 2.69, 3.51, 8.01, 25.7, 96.9, 367.1.

When the appended tenth belongs anywhere in the file, the pieces lose at every buffer smaller than the tail: 3.51 moves a key against 3.24 at a 16th, 8.01 against 6.54 at a 64th. Every piece’s smallest key is near the bottom of the file, so every crossing moves nearly the whole head, and seven crossings of the whole file cost more than one merge with rotation. Insertion loses here by a factor of three at its best buffer and sixty at its worst.

This is the random tenth the earlier pages appended beside the late one, the input a request that reads the input first sized its buffer against. The late tenth was easier for the pieces because it belongs in one fifth of the file. The pieces are a way of exploiting exactly that confinement, and where there is none, they have nothing to exploit.

What the counts depend on

One file, one draw. Each input is one permutation of 65,536 keys, and every number is a count on it. The late tenth’s keys are drawn once from the top fifth, so the piece boundaries fall at the same keys in every design, and the comparisons between designs are exact on this file. The moves are exact counts, so there is no sampling error in them, but another draw of the late tenth would move them a little.

A merge from the right, counted as the library counts. The pieces’ merges into the head write each moved head key and each piece key once. They do not use galloping, which the skip the split already made added to the merge sort’s merges as the trim. A head key below the piece’s smallest key is never touched either way.

The floor is unchanged. The least any sort with one spare cell can move on the late tenth is 0.20 moves a key, the floor the earlier essay computed from the permutation’s displaced keys and its twelve cycles. It is a floor on writes, as the floor a merge cannot reach was a floor on comparisons, and neither design comes near it. The pieces at a 64th move twelve times that, against the merge sort’s fifteen. Most of the distance to the floor is still the tail’s own sort, which neither design avoids.

Still open: how many pieces the head can afford

The pieces won at a 64th by fitting the buffer and lost at smaller buffers by crossing the head too often. Those are two ends of one choice. A sort could merge pieces with each other before merging them into the head: two pieces of a buffer each merge into one of two buffers, which no longer fits, but four pieces merged pairwise and then the pairs into the head cross the head half as often as four pieces would, and pay one more level of merging inside the tail for it.

The cost of a crossing is known before it is made. The pass that finds the head ends at the tail’s first key, and a binary search for the tail’s smallest key in the head says how many head keys every crossing will move. A level of merging inside the tail costs about two moves a tail key; a crossing costs about the number of head keys above the tail’s smallest. The measurement that follows merges the tail’s pieces among themselves until one more level would cost more than the crossings it removes, then merges what is left into the head, and runs it at every buffer and every reach. On the late tenth a crossing moves about ten thousand keys and a level of merging the tail about thirteen thousand, so the rule should merge whenever there are more than three or four pieces. The prediction is that at a 64th it merges the seven pieces once, to four, and saves a little more than the 0.52 a key the pieces save now, and that at a 1,024th it merges the 103 pieces down to four or eight and comes within 20% of the merge sort. It could fail on the rotation it was designed to avoid: merged pieces are larger than the buffer, and their merges into the head rotate again. The question is whether a sort that knows what a crossing costs can always choose between crossing and merging well enough to beat sorting the whole tail first, or whether below some buffer the rotation is the cheapest way across.

Named alongside this one

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

The objects this essay names

Each one links to every other essay that touches it.

Auxiliary spaceData movementHonest limitInsertion sortMerge sortPredictionRotationStable sort