Counting

The digit a pass chooses for itself

A radix sort's digit width trades passes against the streams each pass scatters into, and the best width depends on the keys and the cache together: 8 bits for uniform keys through 32 KB, 11 through 256 KB, 16 for keys with few distinct values. A digit widened because the previous pass's counts were concentrated cannot find it, since in a least-significant-first sort one digit's counts say nothing about the next. One read that counts every byte at once, and a sample to size each field, can. Charged for both, the resulting plan is never worse than 1.23 times the best fixed width on any keys or cache measured. The best single fixed width reaches 1.50, and 11 bits reaches 3.61.

The sort that makes none of them counted radix sort in the same four primitives as every comparison sort and found the dial that decides its cost. A pass counts how many keys have each value of a bb-bit digit, turns the counts into starting positions, and scatters every key to its digit’s position, so it writes into 2b2^b separate streams at once. A wider digit means fewer passes and more streams, and the misses do not fall monotonically with the width. The page’s closing section asked for the width that minimises misses, whether the cache’s capacity in lines predicts it, and whether a sort could find it without being told the cache size — by widening its digit whenever the previous pass’s counts were concentrated in a few buckets.

That last proposal cannot work as stated, and the reason is structural rather than empirical. A least-significant-digit radix sort sorts on the lowest digit first and the highest last. The counts of one pass describe one digit of the keys, and the next pass’s digit is a different, higher set of bits. On keys confined to a small range the low digits are spread evenly and the high digits are concentrated, so the pass that reports concentrated counts is the last one, when nothing is left to widen. The information the rule needs does exist, and it can be had in one read. It just has to be gathered before the first pass rather than after each.

This page gathers it, charges for it, and measures what it buys against every fixed width.

Every access in the trace, the counters included

The earlier page traced the keys and the scratch array and left the counters out, and its figures were drawn through a cache of 64 lines. Both matter here. A pass with a 16-bit digit keeps 65,536 counters, 256 KB of them at four bytes each: a structure larger than the keys, read and bumped once for every key the pass places. A trace without it charges a 16-bit pass nothing for the one structure whose size is the reason a digit cannot simply be widened.

So every access goes in: each key read, each counter incremented while counting, the counters’ prefix sum, and, while scattering, each key read, its counter read and bumped, and its write to the output. The sort ping-pongs between the input and one scratch array, as real implementations do, rather than copying back after every pass. Words are four bytes, lines 64 bytes, and the cache is exact LRU at 512 lines (32 KB, a first-level cache) or 4,096 (256 KB, a second level). There are 65,536 keys of four kinds: uniform 32-bit keys, keys below 2202^{20}, heavy-tailed keys whose values are mostly small, and keys drawn from only 64 distinct values. Every sort is checked sorted.

The best width depends on the cache

On uniform 32-bit keys the best digit is 8 bits through 32 KB (0.76 misses a key) and 11 through 256 KB (0.57); the digits chosen from the counts are 8+8+8+8 and 11+11+10 bits, at 0.86 and 0.63Cache misses a key for a least-significant-digit radix sort of 65,536 uniform 32-bit keys, against the digit width, through two cache sizes. 32 KB: 4 bits 1.50 in 8 passes, 6 bits 1.13 in 6 passes, 8 bits 0.76 in 4 passes, 11 bits 2.76 in 3 passes, 16 bits 5.91 in 2 passes; the plan from the counts, fields of 8+8+8+8 bits, 0.86. 256 KB: 4 bits 1.37 in 8 passes, 6 bits 1.03 in 6 passes, 8 bits 0.70 in 4 passes, 11 bits 0.57 in 3 passes, 16 bits 2.72 in 2 passes; the plan from the counts, fields of 11+11+10 bits, 0.63. Both axes are logarithmic; the ringed points are the plans, placed at their widest field.46811161digit width in bits (passes: 32 ÷ width, rounded up)cache misses per keythrough 32 KBthrough 256 KB65,536 uniform 32-bit keysringed: the plan from the counts
Fig. 1 Cache misses a key sorting 65,536 uniform 32-bit keys, against the digit width, both axes logarithmic. Through 32 KB: 1.50 at 4 bits (8 passes), 1.13 at 6, 0.76 at 8, 2.76 at 11, 5.91 at 16. Through 256 KB: 1.37, 1.03, 0.70, 0.57 and 2.72. The ringed points are the plan chosen from the counts: four 8-bit fields at 0.86 through 32 KB, and fields of 11, 11 and 10 bits at 0.63 through 256 KB.

On uniform keys through 32 KB the best width is 8 bits, at 0.76 misses a key. Through 256 KB it is 11 bits, at 0.57. The same keys want a different digit on a different machine, and each best width is disastrous on the other machine. Eleven bits through 32 KB misses 2.76 times a key, three and a half times the best. Sixteen bits misses 5.91 through 32 KB and 2.72 through 256 KB — the worst setting on both, with only two passes.

The cache’s capacity predicts the best width almost exactly. A pass’s scatter writes into 2b2^b streams, and each stream needs its current line resident until the next key lands in it; the counters need their lines too. At 8 bits there are 256 streams and a 32 KB cache holds 512 lines. At 11 bits there are 2,048 streams and a 256 KB cache holds 4,096 lines. The best width in both cases is the widest whose streams fill half the cache’s lines, leaving the other half for the counters and the reads. One stream a line, the obvious rule the earlier page proposed, overshoots by a bit. One stream per two lines lands on the measured optimum at both sizes, among the widths measured; widths of 9, 10, 12 and 13 bits were not swept, so the rule is located to within a bit or two.

That also explains the sixteen-bit result the earlier page saw, where sixteen bits came out best. Its trace omitted the 65,536 counters, and a 16-bit pass’s cost is mostly those counters: every key reads and writes one of 4,096 lines of counters in a random order. Charged for them, sixteen bits is the worst width measured.

A plan read off the keys

The plan gathers what the widening rule was reaching for, in one extra read, before the first pass.

Correctness and cost come from different evidence, on purpose. One read of the keys counts all four bytes’ values at once, 4 × 256 counters. That is exact, and it is the only thing allowed to remove a pass. A byte whose histogram has one occupied bucket is the same in every key, and a pass over it would move every key to where it already is. Skipping it can never produce an unsorted output. Widths are chosen differently: from the lowest unsorted bit upwards, take the widest field, up to sixteen bits, whose number of distinct values stays within a budget of streams. The distinct count comes from a sample of 2,048 keys, corrected for the fact that a sample of 2,048 can never show more than 2,048 values. A wrong estimate there costs misses and never correctness.

The budget is half the cache’s lines, the rule the sweep just measured. That means the plan is told the cache size. The earlier page hoped for a rule that is not, and the section after the next one says why that hope does not survive.

The pre-pass's byte counts and the digits chosen from them through 32 KB: four 8-bit passes for uniform keys, three for keys below 2²⁰ because their top byte never varies, a 16-bit field across the middle two bytes of heavy-tailed keys because most of them are small, and two 16-bit passes for 64 distinct keysFor each kind of key, the number of distinct values each byte takes over 65,536 keys, counted exactly in one pre-pass (top row, most significant byte on the left), and the digit fields the plan chose through a 32 KB cache (bottom row, widths in bits). Uniform 32-bit keys: bytes 256, 256, 256, 256 values from the top; fields 8 bits at 0, 8 bits at 8, 8 bits at 16, 8 bits at 24. Keys below 2²⁰: bytes 1, 16, 256, 256 values from the top; fields 8 bits at 0, 8 bits at 8, 8 bits at 16. Heavy-tailed keys: bytes 6, 109, 256, 256 values from the top; fields 8 bits at 0, 16 bits at 8, 8 bits at 24. 64 distinct keys: bytes 59, 52, 52, 60 values from the top; fields 16 bits at 0, 16 bits at 16.uniform 32-bit keys256 values256 values256 values256 values8888keys below 2²⁰constant16 values256 values256 values888heavy-tailed keys6 values109 values256 values256 values816864 distinct keys59 values52 values52 values60 values1616bit 31bit 0a byte's distinct valuesa pass's digit, in bits65,536 keys of each kindfields chosen for 32 KB
Fig. 2 For each kind of key, the distinct values each byte takes across 65,536 keys (top row, most significant byte on the left) and the fields the plan chose through 32 KB (bottom row, widths in bits). Uniform: 256 values in every byte, four 8-bit fields. Keys below 2²⁰: the top byte is constant, 16 values in the next, three 8-bit fields. Heavy-tailed: 6, 109, 256, 256 values from the top, fields of 8, 16 and 8 bits. Sixty-four distinct keys: 52 to 60 values a byte, two 16-bit fields.

The plans say what the counts saw. Keys below 2202^{20} have a constant top byte, so the plan makes three passes where every fixed 8-bit sort makes four. Heavy-tailed keys have plenty of distinct values in their low bytes and few in their middle, because most keys are small, so the plan spans the middle two bytes with one 16-bit field. Keys drawn from 64 values have about 55 distinct values in every byte, and any 16-bit window over them still holds at most 64. So the plan takes two 16-bit passes, which only a width chosen for exactly this input would have taken.

On the keys a fixed width was not chosen for

On keys below 2²⁰ the best digit is 8 bits through 32 KB (0.76 misses a key) and 11 through 256 KB (0.54); the digits chosen from the counts are 8+8+8 and 11+13 bits, at 0.66 and 0.45Cache misses a key for a least-significant-digit radix sort of 65,536 keys below 2²⁰, against the digit width, through two cache sizes. 32 KB: 4 bits 1.50 in 8 passes, 6 bits 1.13 in 6 passes, 8 bits 0.76 in 4 passes, 11 bits 1.58 in 3 passes, 16 bits 3.20 in 2 passes; the plan from the counts, fields of 8+8+8 bits, 0.66. 256 KB: 4 bits 1.41 in 8 passes, 6 bits 1.05 in 6 passes, 8 bits 0.70 in 4 passes, 11 bits 0.54 in 3 passes, 16 bits 1.60 in 2 passes; the plan from the counts, fields of 11+13 bits, 0.45. Both axes are logarithmic; the ringed points are the plans, placed at their widest field.46811161digit width in bits (passes: 32 ÷ width, rounded up)cache misses per keythrough 32 KBthrough 256 KB65,536 keys below 2²⁰ringed: the plan from the counts
Fig. 3 The same sweep on 65,536 keys below 2²⁰. Through 32 KB: 1.50, 1.13, 0.76, 1.58 and 3.20 misses a key at 4, 6, 8, 11 and 16 bits; through 256 KB: 1.41, 1.05, 0.70, 0.54 and 1.60. The plan: three 8-bit fields at 0.66 through 32 KB, and fields of 11 and 13 bits at 0.45 through 256 KB — below every fixed width at both.

On keys confined to a small range the plan beats every fixed width through both caches: 0.66 misses a key against the best fixed width’s 0.76 through 32 KB, and 0.45 against 0.54 through 256 KB. It is not choosing a better width. It is doing one pass fewer, because a fixed width must cover all 32 bits whether or not they vary, and only the counts know they do not. A caller who knew its keys were below 2202^{20} could tell a fixed-width sort to stop at 20 bits and get the same saving. A general-purpose sort is not told, and the pre-pass is how it finds out.

Misses a key for every kind of key through both caches: no fixed width is best everywhere, and the digits chosen from the counts are never far from whichever one isFor each kind of key and cache size, cache misses a key for 8-bit and 11-bit digits, for the plan from the counts, and for the best of the fixed widths 4, 6, 8, 11 and 16. Uniform 32-bit keys through 32 KB: 8-bit 0.76, 11-bit 2.76, from the counts 0.86, best fixed width 0.76. Uniform 32-bit keys through 256 KB: 8-bit 0.70, 11-bit 0.57, from the counts 0.63, best fixed width 0.57. Keys below 2²⁰ through 32 KB: 8-bit 0.76, 11-bit 1.58, from the counts 0.66, best fixed width 0.76. Keys below 2²⁰ through 256 KB: 8-bit 0.70, 11-bit 0.54, from the counts 0.45, best fixed width 0.54. Heavy-tailed keys through 32 KB: 8-bit 0.78, 11-bit 1.38, from the counts 0.78, best fixed width 0.78. Heavy-tailed keys through 256 KB: 8-bit 0.72, 11-bit 0.54, from the counts 0.66, best fixed width 0.54. 64 distinct keys through 32 KB: 8-bit 0.76, 11-bit 0.57, from the counts 0.60, best fixed width 0.51. 64 distinct keys through 256 KB: 8-bit 0.70, 11-bit 0.53, from the counts 0.55, best fixed width 0.48. Bars are cut at 3 misses a key.uniform 32-bit keysthrough 32 KB0.762.760.860.76uniform 32-bit keysthrough 256 KB0.700.570.630.57keys below 2²⁰through 32 KB0.761.580.660.76keys below 2²⁰through 256 KB0.700.540.450.54heavy-tailed keysthrough 32 KB0.781.380.780.78heavy-tailed keysthrough 256 KB0.720.540.660.5464 distinct keysthrough 32 KB0.760.570.600.5164 distinct keysthrough 256 KB0.700.530.550.488-bit11-bitfrom the countsbest fixed width65,536 keys, misses a keybars cut at 3
Fig. 4 Misses a key for all four kinds of key through both caches: 8-bit digits, 11-bit digits, the plan, and the best of the fixed widths for that case. Through 32 KB the best fixed width is 8 bits for three kinds of key and 16 bits for 64 distinct keys; through 256 KB it is 11 bits for three and 16 for 64 distinct keys. The plan is within 23% of the best everywhere and below it on keys below 2²⁰.

No fixed width is best everywhere. Eight bits is best through 32 KB on three kinds of key and 49% worse than 16 bits on 64 distinct keys. Eleven bits is best through 256 KB on three kinds and three and a half times the best through 32 KB. The plan is never the worst and never more than 23% above the best, and it pays for that with its pre-pass and its sample. On uniform keys through 32 KB those two cost 0.10 misses a key, and that is the whole of its gap there.

A plan cannot avoid being told the cache size

The earlier page hoped for a rule that finds the width without knowing the cache, and the plates say where that hope fails. The counts tell a sort how many streams a digit would open: 256 for a uniform byte, 16 for a nearly constant one, 64 across any window of the few-values keys. They cannot tell it how many streams the machine can hold, because that is a property of the machine, not of the keys. Uniform keys produce identical counts on both caches, and they want 8 bits on one and 11 on the other.

So a sort that knows nothing about the cache can use the counts for exactly one thing: skipping digits that do not vary, and choosing wider fields where the keys have few values. Both help on every cache. Choosing how wide — the step from 8 to 11 bits on uniform keys, worth 19% through 256 KB — needs the cache’s size in lines, and there is no count that supplies it. The layout that is told nothing measured the other kind of answer: a structure that performs within a constant factor of the tuned one on every cache without being told any size. Radix sort has no such form, because its one parameter is a count of streams and the right count is the cache’s.

Where counts could steer pass by pass

The widening rule is not wrong in every radix sort, only in this one, and the other kind is worth naming because it is where the rule belongs.

A most-significant-digit radix sort works the other way up. Its first pass splits the keys by their top digit into buckets, and every later pass works inside one bucket. There the previous pass’s counts are exactly the information the next pass needs: a bucket holding 40 keys needs no 8-bit pass at all, and a bucket whose keys share their next byte can skip it. Production sorts of this kind switch to a comparison sort for small buckets and choose each bucket’s next digit from its size. That is a threshold somebody typed, of the kind the sort the library ships measured in the sorts real programs call.

The least-significant-digit sort measured here has no buckets to steer, which is why its plan has to be made in advance. Every pass sorts the whole array stably, and the only thing a pass learns is the distribution of one digit across all the keys. The price of that is visible in the plan’s pre-pass, one extra read of every key. The gain is that each pass is a single sequential read and a scatter, with none of the most-significant sort’s recursion. Where an algorithm looks drew the difference between the two shapes of access, and the choice between them is a choice about which kind of adaptivity a sort can afford.

That gives the earlier page’s question a sharper answer than it asked for. A radix sort can choose its digit from its own counts, but only where the counts come before the decision. In a least-significant-first sort that means one pre-pass over the whole key; in a most-significant-first sort it happens bucket by bucket for free. The rule that failed here was not a bad rule. It was a most-significant-first rule applied to a least-significant-first sort.

The worst each rule does

Across four kinds of key and two caches, the digits chosen from the counts are never more than 1.23 times the best fixed width for the case; the best single fixed width, 8-bit, reaches 1.50For each digit rule, its misses a key divided by the best fixed width's for the same keys and cache, at worst over the eight combinations of four kinds of key and two cache sizes, and on average. 4-bit: worst 2.97, mean 2.42; 6-bit: worst 2.23, mean 1.82; 8-bit: worst 1.50, mean 1.22; 11-bit: worst 3.61, mean 1.59; 16-bit: worst 7.73, mean 3.23; from the counts: worst 1.23, mean 1.06.4-bit2.97× · mean 2.426-bit2.23× · mean 1.828-bit1.50× · mean 1.2211-bit3.61× · mean 1.5916-bit7.73× · mean 3.23from the counts1.23× · mean 1.06the best for each caseworst over 4 inputs × 2 cachesmisses ÷ the best fixed width's
Fig. 5 For each digit rule, its misses a key divided by the best fixed width’s for the same keys and cache, at worst and on average over the eight combinations of four kinds of key and two caches. 4-bit: worst 2.97, mean 2.42. 6-bit: 2.23, 1.82. 8-bit: 1.50, 1.22. 11-bit: 3.61, 1.59. 16-bit: 7.73, 3.23. The plan from the counts: 1.23, 1.06.

Summarised as each rule’s worst case over every combination measured, the plan is the only rule never more than a quarter above the best: 1.23 at worst, 1.06 on average. The best single fixed width, 8 bits, is 1.50 at worst, on 64 distinct keys. Its worst case is a missed opportunity rather than a disaster, which is why 8-bit radix sorts are the common default. Eleven bits, which looks best on a large cache, is 3.61 at worst, and sixteen bits, which saves passes, is 7.73.

The plan’s worst case is heavy-tailed keys through 256 KB, 0.66 against 11-bit digits’ 0.54. It spans the middle two bytes with one 16-bit field because the sample saw few distinct values there. The keys the sample is least likely to contain are the rare large ones that give a heavy tail its tail, and the scatter’s streams include them whether the sample saw them or not. A sample-based estimate of distinct values is weakest on skewed data, and here that weakness costs 23%. A result the size of its own noise is the page on what a sample can and cannot say about the rare values it is least likely to contain.

This is a minimax argument, and it has the shape a budget split before the question arrives found for sketches: a choice made before the input is known cannot be best for every input, but it can be never far from the best, and that property is what a default should be judged on. A fixed width is a bet on one kind of key and one machine; the plan reads the keys and is told the machine, and its worst case is set by how well a sample estimates distinct values rather than by which bet was made.

What was measured and what was not

One size of array. 65,536 keys, 256 KB of them. The array itself fits in the larger cache and not the smaller, which is part of why the two caches want different widths; at millions of keys neither cache holds the array and the best widths would move. The earlier page found its miss rates nearly independent of nn at its cache size, and that is not checked here.

Exact LRU, fully associative. Real caches are set-associative, and a scatter into 2,048 streams whose lines are a power of two apart can conflict in sets long before the capacity is full. That would pull the best width on the large cache below 11 bits, and the half-the-lines rule would become a rule about associativity. The cliff where the data stops fitting is the page on how sharply capacity binds; associativity is not modelled anywhere in this collection.

No prefetching, no write-combining buffers. Real radix sorts often scatter through a small buffer per stream and flush whole lines, which turns the scatter’s random writes into sequential ones and moves the best width up. That is a different algorithm with a different trace, and it is the usual way production code widens its digits.

Misses, not time. The plan’s pre-pass and sample are charged their misses. Their arithmetic — four histogram updates a key and a distinct count over a sample — is charged nothing, as the passes’ arithmetic is not, which is the standing gap the count is not the time describes.

Still open: the scatter that buffers its streams

Every plate here charges a scatter as 2b2^b streams writing directly into memory, and the half-the-lines rule is a consequence of that. Production radix sorts usually do something else: each stream writes into a small buffer of one line in a fixed region, and when a buffer is full it is flushed to its stream’s place in the output as one whole line. The random writes then hit 2b2^b buffer lines that stay resident, and the output is written a line at a time.

The measurement that follows adds that buffering to the traced sort, with the buffer region charged like any other memory. It asks whether the best width moves from half the cache’s lines towards all of them, and whether the plan’s budget should follow. The prediction is that buffering helps most at the widest digit whose buffers still fit: through 256 KB, 11-bit digits need 2,048 buffer lines of the 4,096 there are, so they should gain the most there, and 12 bits, which needs all 4,096, should become the new edge. Through 32 KB the 256 buffers of an 8-bit digit fit and the 2,048 of an 11-bit digit do not, so the unbuffered 11-bit figure of 2.76 misses a key should barely move. If that holds, buffering does not remove the cache from the choice of width. It makes each stream cheaper, which lets the width grow until the buffers themselves fill the cache, and moves the rule from half the lines to nearly all of them.

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.

CacheCost modelCounting sortCrossoverDesign parameterHonest limitLocalityMeasured countMemory layoutRadix sortSamplingWorking set