What a bound is

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.

A floor under a product measured the published bound that time times space for sorting is at least n2n^2, in a model where a sort reads its input but may write only in SS cells of its own. A read-only family meets it with equality: keep the SS smallest values above the last one emitted, emit them, and scan the input again, ⌈n/S⌉\lceil n/S \rceil times. Every sort that writes into its input sits far above the bound, because the array it overwrites counts as working memory. A product paid for in blocks then charged the reads in block transfers through a memory of MM cells in lines of BB. The family’s reads are sequential, so its product fell to about n2/Bn^2/B. The quicksorts, whose reads are mostly sequential too, fell alongside it once MM was large, while heapsort’s scattered reads kept it far above.

Its closing section found the gap in the account. The product charges quicksort for the nn cells it writes and says nothing about the MM cells of fast memory it needs to write them cheaply. The family needs almost no fast memory at all. A three-way account of transfers, writable cells and fast memory would separate what the two-way product cannot. It proposed sweeping MM and drawing, for each algorithm, the least fast memory at which its product comes within a stated factor of n2/Bn^2/B. The prediction was that the family’s need is its own buffer, a few hundred cells whatever nn is. Quicksort’s should be n/2cn/2^c for a factor 2c2^c, since each halving of memory adds a level of scans. Heapsort’s should be all of nn.

The first and third parts hold. The second holds in shape and fails in rate. And the account turns up a fourth product the bound never takes, which separates the two kinds of sort more cleanly than either of the products before it.

Every trace replayed at every memory

Each sort runs on random keys and records every array access it makes. The traces are the earlier essay’s, for heapsort, Shellsort and quicksort with a random pivot and with a median-of-three pivot. The family is traced too this time, not charged by formula. Every read of its input is recorded, along with every touch of its sorted buffer (the comparison with its largest entry and the shifts of each insertion), the two bookkeeping cells it reads on every step, and each value of its output as it is written. Every address is separate, so a memory too small for the buffer is charged for it. Each trace is replayed through an LRU memory of MM cells in lines of eight, the block model one access, eight kilobytes introduced to charge an access by the line it lands on, for every MM on a doubling grid from 16 cells to nn, at nn = 2,048, 4,096 and 8,192. When the memory holds the whole array, every sort pays one read of it, which is the floor for anything that must see every key.

The product is transfers times writable cells over n2/Bn^2/B. A sort’s writable cells are its nn array cells plus whatever it allocates; the family’s are its buffer and three bookkeeping cells. With room for the buffer but not the input, the family’s transfers come out at its scans over lines plus one pass of output, the formula the earlier essay charged, which is the check that the trace and the formula describe the same algorithm.

What each sort pays as memory shrinks

What each sort pays in transfers as fast memory shrinks, at n = 8,192: heapsort 8.7 reads of its array at half of n in memory and 42.6 at 512 cells; quicksort 2.7 and 8.5; the read-only family with 16 cells pays about 516 at any memory of 64 cells or more, because it reads the whole array once for every 16 values it emitsBlock transfers in units of n/B — one sequential read of the array — against the fast memory M in cells, lines of 8, LRU, n = 8,192, random input. Heapsort: 16 cells 133, 32 cells 104, 64 cells 100, 128 cells 67, 256 cells 55, 512 cells 43, 1024 cells 30, 2048 cells 18, 4096 cells 8.68, 8192 cells 1.00. Shellsort: 16 cells 211, 32 cells 89, 64 cells 45, 128 cells 31, 256 cells 29, 512 cells 24, 1024 cells 19, 2048 cells 17, 4096 cells 12, 8192 cells 1.00. Quicksort, random pivot: 16 cells 25, 32 cells 17, 64 cells 14, 128 cells 12, 256 cells 10, 512 cells 8.50, 1024 cells 6.63, 2048 cells 5.07, 4096 cells 2.66, 8192 cells 1.00. Quicksort, median of three: 16 cells 27, 32 cells 17, 64 cells 15, 128 cells 13, 256 cells 10, 512 cells 8.75, 1024 cells 7.10, 2048 cells 5.32, 4096 cells 4.45, 8192 cells 1.00. Family, 16 cells: 16 cells 6,461, 32 cells 559, 64 cells 527, 128 cells 521, 256 cells 518, 512 cells 516, 1024 cells 514, 2048 cells 514, 4096 cells 514, 8192 cells 514. Family, 64 cells: 16 cells 1,757, 32 cells 330, 64 cells 240, 128 cells 157, 256 cells 138, 512 cells 131, 1024 cells 130, 2048 cells 129, 4096 cells 129, 8192 cells 129. Family, 256 cells: 16 cells 807, 32 cells 477, 64 cells 451, 128 cells 369, 256 cells 118, 512 cells 36, 1024 cells 33, 2048 cells 33, 4096 cells 33, 8192 cells 33. Both axes are logarithmic.16642561024409611010010³fast memory M, cellstransfers, in reads of the whole arrayheapsortShellsortquicksort, random pivotquicksort, median of threefamily, 16 cellsfamily, 64 cellsfamily, 256 cellsn = 8,192, lines of 8, LRUone transfer a line
Fig. 1 Transfers, in reads of the whole array, against fast memory at n = 8,192, lines of eight. Heapsort: 1 at n, 8.7 at n/2, 43 at 512 cells, 133 at 16. Quicksort, random pivot: 1, 2.7, 8.5, 25. Shellsort: 1, 12, 24, 211. The family with 16 cells: about 516 at 64 cells or more, 6,461 at 16. With 256 cells: 33 at 512 or more, 118 at 256, 369 at 128.

At n = 8,192, quicksort pays 2.7 reads of its array in transfers with half of n in fast memory and 8.5 with 512 cells; heapsort pays 8.7 and 43. Both pay exactly one read once the whole array fits. Below that the curves part by locality, as the earlier essay found by varying the line. Quicksort’s partitions are sequential sweeps, and each halving of memory adds about 1.8 reads of the array, because one more level of the recursion no longer fits. Heapsort’s sift-downs jump across the array, and below half of nn almost every step down the heap is a transfer.

The family’s curves are flat and high. With 16 cells it pays about 516 reads of the array at any memory of 64 cells or more, because it scans the whole input for every 16 values it emits, and no amount of fast memory beyond its buffer changes that. With 256 cells it pays 33 reads, a sixteenth as many. Below its buffer the family falls off a cliff: with 16 cells of memory and a buffer of 16 it pays 6,461 reads, because its buffer, its bookkeeping and the line it is scanning no longer fit together.

The family needs its buffer and a few lines

The read-only family is on the floor as soon as its buffer fits and far off it until then: with 256 cells its product is 11.7 times n²/B in 128 cells of fast memory, 3.7 in 256, and 1.13 in 512, whatever n is — its need for fast memory is its own buffer and a few linesThe read-only family's block transfers times its writable cells (buffer plus three bookkeeping cells), over n²/B, against fast memory, n = 8,192, lines of 8. 16 cells: 16 14.98, 32 1.30, 64 1.22, 128 1.21, 256 1.20, 512 1.20, 1024 1.19, 2048 1.19, 4096 1.19, 8192 1.19. 64 cells: 16 14.37, 32 2.70, 64 1.96, 128 1.28, 256 1.13, 512 1.07, 1024 1.06, 2048 1.06, 4096 1.06, 8192 1.06. 256 cells: 16 25.51, 32 15.09, 64 14.27, 128 11.67, 256 3.73, 512 1.13, 1024 1.06, 2048 1.05, 4096 1.05, 8192 1.05. Both axes are logarithmic.166425610244096110fast memory M, cellstransfers × writable cells, per n²/Bbuffer of 16buffer of 64buffer of 256n = 8,192; vertical rules: each bufferthe floor at one
Fig. 2 The family’s transfers times writable cells, over n2/Bn^2/B, against fast memory, n = 8,192. Buffer of 16: 15.0 at 16 cells, 1.30 at 32, 1.19 from 1,024 up. Buffer of 64: 2.70 at 32, 1.96 at 64, 1.28 at 128, 1.06. Buffer of 256: 11.7 at 128, 3.73 at 256, 1.13 at 512, 1.05. The vertical rules mark each buffer.

The family is on the floor as soon as its buffer fits: with 256 cells its product is 11.7 times n2/Bn^2/B in 128 cells of fast memory, 3.73 in 256 and 1.13 in 512, and the same holds at every length. That is the prediction’s first part. The family’s need for fast memory is its own buffer plus the few lines its scan, its output and its bookkeeping occupy, and it does not grow with nn.

The curves show one refinement. A buffer does not have to fit entirely to be nearly free. With 64 cells of buffer and only 32 cells of fast memory, the product is 2.70 times the floor, not ten or fifteen. Between insertions the family touches only the buffer’s last entry, the largest value it is keeping, and late in each scan insertions are rare, because a value small enough to enter a full buffer of the smallest values becomes harder to find as the scan proceeds. The working set of the buffer is its end, most of the time. The cliff comes where even that working set stops fitting, which for a buffer of 256 is between 128 and 256 cells.

Heapsort needs all of n, quicksort an eighth

The least fast memory that keeps each product within 8 times n²/B: heapsort and Shellsort need all of n at every length, quicksort with a random pivot needs 1/8 of it — 256, 512, 1,024 cells at n = 2,048, 4,096, 8,192 — and the read-only family needs 32 cells with a buffer of 16 and 256 with 256, at every lengthFor each algorithm and each n, the smallest fast memory on a doubling grid at which block transfers times writable cells is at most 8 times n²/B, lines of 8, LRU. Heapsort: n 2,048 2,048, n 4,096 4,096, n 8,192 8,192. Shellsort: n 2,048 2,048, n 4,096 4,096, n 8,192 8,192. Quicksort, random pivot: n 2,048 256, n 4,096 512, n 8,192 1,024. Quicksort, median of three: n 2,048 256, n 4,096 512, n 8,192 1,024. Family, 16 cells: n 2,048 32, n 4,096 32, n 8,192 32. Family, 64 cells: n 2,048 32, n 4,096 32, n 8,192 32. Family, 256 cells: n 2,048 256, n 4,096 256, n 8,192 256. Both axes are logarithmic.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
Fig. 3 The least fast memory on a doubling grid that keeps each product within eight times n2/Bn^2/B, at n = 2,048, 4,096 and 8,192. Heapsort and Shellsort: all of n. Quicksort, both pivots: 256, 512 and 1,024 cells, an eighth of n. The family: 32 cells with a buffer of 16 or 64, 256 with a buffer of 256, at every length.

Within a factor of eight of the floor, heapsort and Shellsort need all of nn in fast memory at every length, quicksort needs an eighth of nn, and the family needs 32 to 256 cells whatever nn is. The plate’s three slopes are the three predictions. The family’s need is flat. Quicksort’s and heapsort’s grow in proportion to nn, with quicksort’s an eighth of heapsort’s.

The flat line is the plate’s argument. A product within a factor of eight of n2/Bn^2/B is, for the earlier essays, the same verdict for all of these algorithms: each is near the floor. The memory each needs to reach it ranges from 32 cells to 8,192. Doubling nn doubles what quicksort and heapsort require and leaves the family’s requirement where it was. Any account of time and space that leaves fast memory out describes these algorithms as equals, and the plate shows they are not.

Heapsort’s need is all of nn for the reason the cliff where the data stops fitting gave for a random access pattern: below the size of the working set, LRU misses on nearly every access, and heapsort’s working set is the whole heap. Shellsort behaves the same way at this factor because its long gaps make the same kind of jumps. The two pay one read of the array at M=nM = n and 8.7 and 12 reads at n/2n/2, so a factor of eight is crossed just short of fitting the whole array.

Each factor of two buys a sixteenth

At n = 8,192, each doubling of the factor allowed above the floor cuts quicksort's need for fast memory — 8,192 cells within 2, 4,096 cells within 4, 1,024 cells within 8, 64 cells within 16 — while heapsort's stays at all of n until the factor reaches sixteen, and the family's is its buffer throughoutThe least fast memory, on a doubling grid, at which each algorithm's block transfers times writable cells is within a factor of 2, 4, 8 and 16 of n²/B, n = 8,192, lines of 8. Heapsort: 2 8,192, 4 8,192, 8 8,192, 16 4,096. Shellsort: 2 8,192, 4 8,192, 8 8,192, 16 4,096. Quicksort, random pivot: 2 8,192, 4 4,096, 8 1,024, 16 64. Quicksort, median of three: 2 8,192, 4 8,192, 8 1,024, 16 64. Family, 16 cells: 2 32, 4 32, 8 32, 16 16. Family, 64 cells: 2 64, 4 32, 8 32, 16 16. Family, 256 cells: 2 512, 4 256, 8 256, 16 32.within 2within 4within 8within 16161281,0248,192heapsortShellsortquicksort, random pivotquicksort, median of threefamily, 16 cellsfamily, 64 cellsfamily, 256 cellsleast fast memory, cells (logarithmic)n = 8,192, lines of 8one bar a factor
Fig. 4 At n = 8,192, the least fast memory within a factor of 2, 4, 8 and 16 of n2/Bn^2/B. Quicksort, random pivot: 8,192, 4,096, 1,024 and 64 cells. Median of three: 8,192, 8,192, 1,024, 64. Heapsort and Shellsort: 8,192, 8,192, 8,192, 4,096. The family: 64 cells or less with buffers of 16 and 64 at every factor; with 256, 512, 256, 256 and 32.

At n = 8,192, quicksort’s need for fast memory falls from 8,192 cells within a factor of two to 4,096 within four, 1,024 within eight and 64 within sixteen. The prediction said each extra factor of two would halve it. It does far more than halve: the step from four to eight divides it by four, and the step from eight to sixteen by sixteen.

The transfers plate says why. Each halving of memory adds about 1.8 reads of the array to quicksort’s bill, a constant amount, while the factor allowed above the floor is a multiple. Allowing twice the factor allows roughly twice as many extra reads, and each extra 1.8 reads buys a halving of memory. So the memory quicksort needs falls exponentially in the factor, roughly as n⋅2−(f−1)/1.8n \cdot 2^{-(f-1)/1.8}: about n/3n/3 at a factor of four, n/15n/15 at eight and n/300n/300 at sixteen, against the grid’s n/2n/2, n/8n/8 and n/128n/128. The prediction’s n/2cn/2^c treated the factor as the number of levels; it is the number of levels times the cost of a level, and the cost of a level for a partition sort is about two reads of the array. That is the external-memory picture sorting what will not fit measured for merge sort, where transfers grow with the logarithm of the data’s size over the memory’s, so that a memory many times smaller costs only a few more passes.

Heapsort barely moves: all of nn within eight, half of nn within sixteen. Its transfers at half of nn are 8.7 reads and at a quarter 18, so its bill doubles with each halving of memory, and a factor that doubles buys back only one halving. The difference between the two sorts is the difference between a cost that grows additively as memory shrinks and one that grows multiplicatively.

The product the floor does not take

Transfers times fast memory, the product the bound does not take: the read-only family never falls below 1.3 times n²/B, because it holds no more than its memory between scans, while quicksort falls to 0.05 at 16 cells — writing into its own input is what lets it carry the rest of the array between readsBlock transfers times the fast memory M, over n²/B, against M, n = 8,192, lines of 8. Heapsort: 16 0.26, 32 0.41, 64 0.78, 128 1.05, 256 1.73, 512 2.66, 1024 3.75, 2048 4.60, 4096 4.34, 8192 1.00. Shellsort: 16 0.41, 32 0.35, 64 0.35, 128 0.49, 256 0.90, 512 1.48, 1024 2.35, 2048 4.21, 4096 5.95, 8192 1.00. Quicksort, random pivot: 16 0.05, 32 0.07, 64 0.11, 128 0.19, 256 0.32, 512 0.53, 1024 0.83, 2048 1.27, 4096 1.33, 8192 1.00. Family, 16 cells: 16 12.62, 32 2.18, 64 4.12, 128 8.15, 256 16.20, 512 32.25, 1024 64.30, 2048 128.42, 4096 256.75, 8192 513.50. Family, 64 cells: 16 3.43, 32 1.29, 64 1.87, 128 2.45, 256 4.32, 512 8.21, 1024 16.20, 2048 32.30, 4096 64.57, 8192 129.13. Family, 256 cells: 16 1.58, 32 1.86, 64 3.53, 128 5.77, 256 3.69, 512 2.24, 1024 4.18, 2048 8.29, 4096 16.54, 8192 33.06. Both axes are logarithmic.1664256102440960.1110100fast memory M, cellstransfers × fast memory, per n²/BheapsortShellsortquicksort, random pivotfamily, 16 cellsfamily, 64 cellsfamily, 256 cellsn = 8,192, lines of 8the rule at one: n²/B
Fig. 5 Transfers times fast memory, over n2/Bn^2/B, against fast memory, n = 8,192. The family never falls below 1.29 (buffer of 64 at 32 cells); 2.18 at best with a buffer of 16. Quicksort with a random pivot: 0.05 at 16 cells, 0.32 at 256, 0.83 at 1,024, 1.27 at 2,048. Heapsort: 0.26 at 16 cells, 1.05 at 128, 4.6 at 2,048. Shellsort: 0.35 at its least.

Transfers times fast memory never falls below 1.29 times n2/Bn^2/B for the read-only family at any memory or buffer measured, and for quicksort it falls to 0.05 at 16 cells of memory. This is the product the closing section’s three-way account implied and did not name. It separates the two kinds of sort completely, where transfers times writable cells put them side by side.

The family’s floor here has a one-line reason. A sort that may not write into its input carries information from one scan to the next only in the memory it holds, and it cannot usefully hold more than MM values in MM cells of fast memory, so each scan emits at most about MM values. It needs at least n/Mn/M scans of n/Bn/B transfers each, and transfers times MM is at least n2/Bn^2/B. It is the published argument with the fast memory in the place of SS: for a read-only sort, the cells it writes and the cells it holds fast are the same cells.

Quicksort escapes that argument because it writes into its input. The arrangement of the array after a partition is information carried between scans, stored in slow memory, and a sort that can store its progress there needs only enough fast memory to keep its current sweeps efficient. Its transfers grow as the logarithm of n/Mn/M while MM shrinks geometrically, so the product of the two keeps falling, from 1.27 at 2,048 cells to 0.05 at 16. Its partitions never consult MM at all; like the layout the layout that is told nothing measured, a sweep that is sequential at every scale is efficient at every memory without being told which one it has. Heapsort writes into its input too and gets the same escape at small memories, 0.26 at 16 cells, though its transfers grow too fast for the product to stay low as memory grows.

That is the trade the closing section was reaching for, now stated as two products. Transfers times writable cells has a floor of n2/Bn^2/B that every sort here can reach, given enough fast memory. Transfers times fast memory has the same floor for any sort that does not write into its input, and no floor at that level for one that does. A sort can save writable cells or fast memory, and the read-only family and quicksort are the two extremes: one writes almost nothing and needs fast memory in proportion to the progress it makes each scan, the other writes everything and needs almost no fast memory at all.

The three-way account

Three algorithms on the same footing — each product within 4 times n²/B at n = 8,192 — and three different bills: heapsort writes 8,193 cells with 8,192 in fast memory and 1,024 transfers; quicksort 8,221, 4,096 and 2,725; the family with 16 cells writes 19, holds 32 and pays 572,132 transfersFor each algorithm at the least fast memory that keeps its product within 4 times n²/B, n = 8,192, lines of 8: its writable cells, that fast memory, and its block transfers there. Heapsort: 8,193 writable, 8,192 fast, 1,024 transfers. Shellsort: 8,193 writable, 8,192 fast, 1,024 transfers. Quicksort, random pivot: 8,221 writable, 4,096 fast, 2,725 transfers. Quicksort, median of three: 8,218 writable, 8,192 fast, 1,024 transfers. Family, 16 cells: 19 writable, 32 fast, 572,132 transfers. Family, 64 cells: 67 writable, 32 fast, 337,676 transfers. Family, 256 cells: 259 writable, 256 fast, 120,788 transfers. Each column is logarithmic.writable cellsfast memory, cellstransfersheapsort8,1938,1921,024Shellsort8,1938,1921,024quicksort, random pivot8,2214,0962,725quicksort, median of three8,2188,1921,024family, 16 cells1932572,132family, 64 cells6732337,676family, 256 cells259256120,788n = 8,192, within 4 × n²/Blogarithmic columns
Fig. 6 At n = 8,192, each algorithm at the least fast memory that keeps its product within four times n2/Bn^2/B: writable cells, fast memory, transfers. Heapsort: 8,193, 8,192, 1,024. Quicksort, random pivot: 8,221, 4,096, 2,725. The family with a buffer of 16: 19, 32, 572,132; with 256: 259, 256, 120,788.

All three algorithms are within a factor of four of the floor at n = 8,192, and their bills differ by a factor of 430 in writable cells, 256 in fast memory and 560 in transfers. Heapsort writes all 8,193 cells, needs all 8,192 in fast memory and pays 1,024 transfers, one read of its array. Quicksort writes 8,221, needs 4,096 and pays 2,725. The family with a buffer of 16 writes 19 cells, needs 32 and pays 572,132 transfers. With a buffer of 256 it writes 259, needs 256 and pays 120,788.

No single product ranks these. The two-way product calls them equal. Transfers alone favour heapsort with all of nn in fast memory, which is the case where sorting is cheap anyway. Fast memory alone favours the family, at the price of five hundred passes. The account the closing section asked for is three numbers, and which algorithm is best depends on which resource is scarce. The model a bound was quoted in drew the same lesson from the accuracy guarantees of streaming summaries: a guarantee holds in the model it was stated in, and one assumption more is enough to change which structure wins.

What the prediction got right

The closing section predicted that the family’s need is its own buffer, a few hundred cells whatever nn is. It is its buffer, and slightly less, since the buffer’s working set is its end, and it does not move with nn. It predicted heapsort needs all of nn, and within a factor of eight it does at every length. It predicted quicksort’s need would be n/2cn/2^c for a factor 2c2^c. The need is proportional to nn, as predicted, but it falls with the factor much faster than halving, because a level of quicksort’s recursion costs about 1.8 reads of the array, not one.

What it could not have predicted is the product of transfers and fast memory, and that turns out to be the cleanest statement of the trade. The bound on time times writable space was proved for sorts that read their input and write elsewhere. Read with fast memory in place of writable space, the same argument holds for exactly those sorts and fails for every sort that writes in place. In-place sorting is a way of using slow memory as storage for progress, and the published bound charges that storage as if it were free.

What the replay assumes

LRU on one level. Every transfer is a miss in a fully associative LRU memory of MM cells. A real cache is set-associative and a real memory hierarchy has several levels; the block that is not a block listed what such a model leaves out. Within the model the comparisons are exact, and the cliff and slopes are the shapes LRU gives every scan and every random walk.

Reads only. A transfer is charged when a line is brought in. A line a sort has written must also be written back when it is evicted, and quicksort dirties every line it partitions while the family dirties only its output. Write-backs were not charged.

Random keys. Every trace is on a random permutation. Quicksort’s need for memory depends on its partitions being balanced, and the median-of-three pivot, which needed all of nn within a factor of four at n = 8,192 against the random pivot’s half, shows that the need moves with the pivot rule on the same input.

Still open: the lines a sort writes back

The replay charged a transfer for every line brought into fast memory and none for a line written out. That is fair to the family, which writes only its output, once, in order. It flatters every sort that writes in place, since each line a partition or a sift-down modifies must eventually go back to slow memory, and under LRU it goes back each time it is evicted dirty.

The measurement that follows replays the same traces with every eviction of a modified line charged as a second transfer, and draws the same two products again: transfers times writable cells against n2/Bn^2/B, and transfers times fast memory. The prediction is that write-backs roughly double quicksort’s transfers at small memories, since a partition writes nearly every line it reads, and nearly triple heapsort’s, whose sift-downs modify lines they leave quickly. The family’s transfers should not move. On transfers times fast memory, quicksort’s least value should rise from 0.05 to about 0.1, still far below the family’s 1.29. The question is whether charging the writes moves the in-place sorts back towards the floor the read-only family cannot get under, or whether storing progress in slow memory stays cheap once its true price is paid.

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.

Block transferCost modelExternal-memoryIn placeLocalityLower boundTime space tradeoffWorking set