What the machine does

Overflow holds the old keys

A cuckoo table that kicks blind under a cap of two leaves 9% of its keys in overflow, and every way of searching harder for room leaves fewer. Charged for its lookups as well as its insertions, the overflow was predicted to make a deeper search pay from ten lookups an insertion. It does not: blind kicks under a cap of two are the cheapest table up to thirty lookups an insertion when stored keys are asked for evenly, and at every mix measured when they are asked for by recency. Searching harder also moves keys into their second buckets, which every lookup pays for, and the keys a kick leaves in overflow are the old ones, not the new.

A look that spreads the room compared the ways a cuckoo table can make room for an insertion that meets a full bucket. The table holds 16,384 entries in buckets of eight, each key in one of two buckets its hashes choose. Blind kicks move a random resident to its other bucket, up to a cap, after which the displaced key is filed in its region’s overflow block; a look reads the residents’ other buckets first and moves one with room; a two-level look searches the residents of those buckets too. Every design was judged on two numbers, the share of keys it leaves in overflow and the bucket reads an insertion pays, and at 95% load they ranged from blind kicks under a cap of two, 9.2% in overflow for 1.63 reads, to the two-level look, nothing in overflow for 4.31.

Its closing section noticed that the exchange rate between the two numbers had been left open. A key in overflow is not free to keep, even in a region’s spare entries: a lookup of it reads both its buckets, finds nothing, and reads the overflow block, three reads where a key in its first bucket costs one, and it pays them every time the key is asked for. A table read ten times for every write should spend reads at insertion to keep keys out of overflow. The prediction was that at one lookup an insertion the first-found look under a cap of two would be the cheapest table at 95% load, and that by ten lookups an insertion the two-level look would be cheapest at every load from 95% up. It named the risk that the keys in overflow are the most recently inserted, and that recent keys are the ones looked up most.

The overflow’s price is real and smaller than the price nobody had counted. And the named risk runs the other way.

Charging the lookups

The table and its churn are the earlier essays’ exactly: the fill to the load, then eight rounds of churn in which a random key is deleted and a fresh one inserted, keys the cap stops filed in their region’s overflow block and staying there. Seven designs are run: blind kicks under caps of 2, 8 and 32; the first-found look under caps of 0 and 2; the emptiest-bucket look under a cap of 2; and the two-level look. At the end of the churn every stored key’s place is read off the table, its first bucket, its second, or overflow, together with when it was inserted.

A lookup of a stored key reads its first bucket, then its second if the key is not there, then its region’s overflow block if it is in neither: one, two or three reads. A lookup of an absent key reads both buckets, and the overflow block when its region’s block holds anything, with no filter in front of it to turn the lookup away. Lookups of stored keys are drawn either evenly over the stored keys or in proportion to 1/(r+1)1/(r+1) for a key’s rank rr by recency, the newest first. A design’s cost is its insertions’ reads plus, for each insertion, the reads of a given number of lookups, from a tenth to a hundred.

Blind kicks, nearly everywhere

With stored keys asked for evenly, at 95% load, blind kicks under a cap of two are the cheapest table at every mix up to 30 lookups an insertion, although they leave 9.3% of keys in overflow: at ten lookups an insertion they cost 15.83 reads an insertion against 17.95 for the first-found look and 18.10 for the two-level lookThe churned table at 95% load: bucket reads per operation — an insertion's reads plus the lookups' reads for each insertion, divided by the operations — against the lookups for every insertion, for each design, lookups of stored keys asked for evenly. 0.1 lookups: blind2 1.77, blind32 4.37, first2 4.47, emptiest2 7.26, deep 4.45, cheapest blind2; 0.3 lookups: blind2 2.05, blind32 4.65, first2 4.74, emptiest2 7.53, deep 4.72, cheapest blind2; 1 lookups: blind2 3.05, blind32 5.65, first2 5.70, emptiest2 8.47, deep 5.69, cheapest blind2; 3 lookups: blind2 5.89, blind32 8.50, first2 8.42, emptiest2 11.17, deep 8.44, cheapest blind2; 10 lookups: blind2 15.83, blind32 18.48, first2 17.95, emptiest2 20.60, deep 18.10, cheapest blind2; 30 lookups: blind2 44.23, blind32 47.00, first2 45.17, emptiest2 47.55, deep 45.67, cheapest blind2; 100 lookups: blind2 143.63, blind32 146.80, first2 140.44, emptiest2 141.88, deep 142.19, cheapest first2. Totals are reads an insertion including its lookups.246lookups for every insertion (log scale)bucket reads an operation0.10.3131030100blind kicks, cap of 2blind kicks, cap of 32look, first with room, cap 2look, the emptiest, cap 2two-level look95% load, stored keys asked for evenlycheapest: blind2, first2
Fig. 1 At 95% load, stored keys asked for evenly: bucket reads an operation against lookups an insertion. At ten lookups an insertion, blind kicks under a cap of two cost 15.83 reads an insertion, the first-found look 17.95, the two-level look 18.10, the emptiest-bucket look 20.60. Blind kicks are the cheapest up to thirty lookups an insertion; at a hundred the first-found look is.

At 95% load, with stored keys asked for evenly, blind kicks under a cap of two are the cheapest table at every mix up to thirty lookups an insertion: 15.83 reads an insertion at ten lookups, against 17.95 for the first-found look and 18.10 for the two-level look. The prediction expected the deeper search to have paid by then. It pays only past about 46 lookups an insertion against the first-found look and 65 against the two-level look, and at a hundred lookups an insertion the first-found look under a cap of two is the cheapest.

The arithmetic is two differences. The two-level look spends 2.68 more reads an insertion than blind kicks under a cap of two, and saves 0.041 reads a lookup; the first-found look spends 2.71 more and saves 0.059. A lookup saving of four or six hundredths of a read takes fifty or sixty lookups to repay three reads at insertion. The prediction priced the saving at what overflow costs, two extra reads for each of 9% of keys, about 0.18 reads a lookup. The measured saving is a third of that or less.

Where the keys live

At 95% load, every way of searching harder for room moves keys into their second buckets: blind kicks under a cap of two leave 67% of keys in their first bucket and 9.3% in overflow, the two-level look 62% and 0.0% — so a lookup of a stored key reads 1.420 buckets under the first and 1.379 under the secondAfter eight rounds of churn at 95% load, the share of stored keys in their first bucket, their second and overflow, and the mean reads of a lookup of a stored key asked for evenly (one, two and three by where it lives), for each design. blind kicks, cap of 2: 67.3%, 23.5%, 9.3%; 1.420 reads; blind kicks, cap of 8: 59.0%, 38.3%, 2.7%; 1.436 reads; blind kicks, cap of 32: 57.4%, 42.6%, 0.0%; 1.426 reads; look, first with room, cap 0: 64.7%, 32.3%, 3.0%; 1.383 reads; look, first with room, cap 2: 64.1%, 35.6%, 0.2%; 1.361 reads; look, the emptiest, cap 2: 65.3%, 34.6%, 0.1%; 1.348 reads; two-level look: 62.1%, 37.9%, 0.0%; 1.379 reads.first bucketsecond bucketoverflow0%25%50%75%100%blind kicks, cap of 2blind kicks, cap of 8blind kicks, cap of 32look, first with room, cap 0look, first with room, cap 2look, the emptiest, cap 2two-level look95% load, after churnshare of stored keys
Fig. 2 At 95% load, the share of stored keys in their first bucket, their second and overflow, and a lookup’s mean reads. Blind kicks, cap 2: 67.3%, 23.5%, 9.3%; 1.420 reads. Cap 32: 57.4%, 42.6%, 0.0%; 1.426. First-found look, cap 2: 64.1%, 35.6%, 0.2%; 1.361. Emptiest, cap 2: 65.3%, 34.6%, 0.1%; 1.348. Two-level look: 62.1%, 37.9%, 0.0%; 1.379.

Every way of searching harder for room moves keys into their second buckets: blind kicks under a cap of two leave 67.3% of keys in their first bucket and 23.5% in their second, the two-level look 62.1% and 37.9%, and blind kicks under a cap of 32 only 57.4% in their first. A key in its second bucket costs a lookup two reads, and a search that keeps a key out of overflow does it by moving residents along their chains, each move sending a key to its other bucket, which half the time is its second.

So the overflow’s saving is partly given back. Blind kicks under a cap of 32 leave essentially nothing in overflow and their lookups cost 1.426 reads, more than under a cap of two at 1.420, because the 9% they keep out of overflow they mostly keep in second buckets, along with a tenth of the table’s other keys that their long chains moved there. The looks do better because they move one resident a step to a bucket that has room, rather than a chain of residents to wherever the chain leads; the first-found look leaves 64.1% of keys in their first bucket, and its lookups are the cheapest of any design but the emptiest-bucket look.

The two costs can be set side by side, read for read. Under blind kicks with a cap of two a lookup of a stored key pays 0.186 reads beyond its first bucket for the keys in overflow, two extra reads each for 9.3% of them, and 0.235 for the keys in their second buckets. Under the two-level look it pays nothing for overflow and 0.379 for second buckets. The deeper search removes 0.186 reads a lookup of overflow and adds 0.144 of second buckets: it gives back three quarters of what it saves. The first-found look gives back less, since its single-step moves leave more keys at home, and nets 0.059 a lookup, the largest saving of any design over blind kicks at a cap of two and still only about a forty-sixth of its own extra cost at insertion.

The keys a capped chain leaves behind counted the keys a chain strands. The other half of what a chain does is the keys it relocates, and on a table whose lookups try the first bucket first, a relocation costs as surely as a stranding does, at half the price and on many more keys.

The newest keys stay home

Overflow holds the old keys, not the new: under blind kicks at a cap of two the newest tenth of the keys has 0.6% in overflow and the oldest tenth 26.1%, and the newest have 4% in their second bucket against 35% — a kick displaces a resident, and the key it stops is one that was already thereAt 95% load after churn, for each tenth of the stored keys ordered by when they were inserted, newest first: the share in overflow (solid) and in their second bucket (dashed), under blind kicks at a cap of two and the first-found look at a cap of two. blind2: 1 0.6% and 4%, 2 1.4% and 10%, 3 2.6% and 16%, 4 4.6% and 17%, 5 5.1% and 26%, 6 9.2% and 27%, 7 11.4% and 31%, 8 13.7% and 33%, 9 17.7% and 36%, 10 26.1% and 35%. first2: 1 0.0% and 6%, 2 0.0% and 16%, 3 0.1% and 23%, 4 0.2% and 32%, 5 0.2% and 37%, 6 0.1% and 45%, 7 0.4% and 48%, 8 0.5% and 50%, 9 0.4% and 50%, 10 0.5% and 48%.00.2000.400tenth of the stored keys by age, newest firstshare of the tenth12345678910blind kicks, cap of 2look, first with room, cap 2solid: overflow · dashed: second95% load, after churnnewest keys on the left
Fig. 3 At 95% load, each tenth of the stored keys by age, newest first: the share in overflow (solid) and in the second bucket (dashed). Blind kicks, cap 2: overflow 0.6% in the newest tenth, 26.1% in the oldest; second bucket 4% and 35%. First-found look, cap 2: overflow none in the newest tenth and 0.4% in the oldest.

Under blind kicks with a cap of two, the newest tenth of the stored keys has 0.6% of its keys in overflow and 4% in their second buckets; the oldest tenth has 26.1% and 35%. The closing section named as its risk that overflow would hold the most recent keys, which are the ones a cache-like workload asks for most. It holds the oldest.

The reason is in what a kick does. A key being inserted takes a slot in its first bucket if there is room, and otherwise displaces a resident, and the key that ends a chain under the cap, the one filed in overflow, is the last resident displaced, not the new key. A new key starts life in its first bucket nearly always, and is moved out only when a later insertion’s chain passes through it. The longer a key has been in the table, the more chains have had the chance to move it, so a key’s place drifts outward with its age: first bucket, then second, then overflow. Under the first-found look the drift is the same and slower, since a look moves one resident a step rather than a chain.

With stored keys asked for by recency, at 98% load, blind kicks under a cap of two are the cheapest table at every mix up to 100 lookups an insertion, although they leave 10.7% of keys in overflow: at ten lookups an insertion they cost 12.59 reads an insertion against 17.25 for the first-found look and 21.22 for the two-level lookThe churned table at 98% load: bucket reads per operation — an insertion's reads plus the lookups' reads for each insertion, divided by the operations — against the lookups for every insertion, for each design, lookups of stored keys asked for by recency. 0.1 lookups: blind2 1.80, blind32 7.31, first2 6.40, emptiest2 9.57, deep 10.25, cheapest blind2; 0.3 lookups: blind2 2.01, blind32 7.54, first2 6.62, emptiest2 9.79, deep 10.47, cheapest blind2; 1 lookups: blind2 2.78, blind32 8.35, first2 7.39, emptiest2 10.56, deep 11.25, cheapest blind2; 3 lookups: blind2 4.96, blind32 10.66, first2 9.58, emptiest2 12.74, deep 13.47, cheapest blind2; 10 lookups: blind2 12.59, blind32 18.76, first2 17.25, emptiest2 20.38, deep 21.22, cheapest blind2; 30 lookups: blind2 34.41, blind32 41.88, first2 39.16, emptiest2 42.23, deep 43.39, cheapest blind2; 100 lookups: blind2 110.75, blind32 122.81, first2 115.86, emptiest2 118.67, deep 120.98, cheapest blind2. Totals are reads an insertion including its lookups.246810lookups for every insertion (log scale)bucket reads an operation0.10.3131030100blind kicks, cap of 2blind kicks, cap of 32look, first with room, cap 2look, the emptiest, cap 2two-level look98% load, stored keys asked for by recencycheapest: blind2
Fig. 4 At 98% load, stored keys asked for by recency: reads an operation against lookups an insertion. At ten lookups an insertion blind kicks under a cap of two cost 12.59 reads an insertion, the first-found look 17.25, the two-level look 21.22. Blind kicks are the cheapest at every mix up to a hundred.

When stored keys are asked for in proportion to their recency, blind kicks under a cap of two are the cheapest table at every mix measured, up to a hundred lookups an insertion, at every load: at 98% load and ten lookups an insertion, 12.59 reads an insertion against 17.25 for the first-found look and 21.22 for the two-level look. A lookup by recency costs 1.09 reads under blind kicks with a cap of two and 1.11 under the two-level look, because the keys being asked for are the ones that have not yet been moved, and the cheapest design to insert is also the cheapest to read.

With absent keys, at 95% load, keeping keys out of overflow pays from about 10 lookups an insertion: an absent key costs two reads where no region has overflow and three where every region does, and the two-level look leaves almost noneThe churned table at 95% load: bucket reads per operation — an insertion's reads plus the lookups' reads for each insertion, divided by the operations — against the lookups for every insertion, for each design, lookups of absent keys. 0.1 lookups: blind2 1.93, blind32 4.43, first2 4.56, emptiest2 7.33, deep 4.51, cheapest blind2; 0.3 lookups: blind2 2.53, blind32 4.83, first2 5.02, emptiest2 7.76, deep 4.91, cheapest blind2; 1 lookups: blind2 4.63, blind32 6.24, first2 6.60, emptiest2 9.23, deep 6.31, cheapest blind2; 3 lookups: blind2 10.63, blind32 10.27, first2 11.13, emptiest2 13.45, deep 10.31, cheapest blind32; 10 lookups: blind2 31.63, blind32 24.38, first2 26.99, emptiest2 28.22, deep 24.31, cheapest deep; 30 lookups: blind2 91.63, blind32 64.69, first2 72.30, emptiest2 70.40, deep 64.31, cheapest deep; 100 lookups: blind2 301.63, blind32 205.79, first2 230.90, emptiest2 218.06, deep 204.31, cheapest deep. Totals are reads an insertion including its lookups.246lookups for every insertion (log scale)bucket reads an operation0.10.3131030100blind kicks, cap of 2blind kicks, cap of 32look, first with room, cap 2look, the emptiest, cap 2two-level look95% load, absent keyscheapest: blind2, blind32, deep
Fig. 5 At 95% load, lookups of absent keys: reads an operation against lookups an insertion. An absent key costs blind kicks under a cap of two three reads, since every region’s overflow block holds something; the two-level look two. The two-level look is the cheapest from ten lookups an insertion.

For lookups of keys that are not in the table, the two-level look is the cheapest design from ten lookups an insertion at every load from 95% up. This is the prediction’s second half, true for the one kind of lookup the overflow cannot hide from. An absent key must be looked for everywhere it could be, both buckets and, if its region’s overflow block holds anything, the block. Under blind kicks with a cap of two every region’s block holds something, so every absent lookup reads three; under the two-level look almost no region’s does, and it reads two. A saving of a whole read a lookup repays the deeper search’s 2.7 reads at insertion in three lookups, and by ten lookups an insertion nothing else comes close.

The overflow blocks’ occupancy is what an absent lookup pays for, and it falls much faster than the overflow share. Under blind kicks with a cap of two every one of the 128 regions holds something in its block at 95% load; under the first-found look with a cap of two, 0.24% of keys are in overflow but they are spread over 27% of the regions, and an absent key costs 2.27 reads; under the emptiest-bucket look 11% of regions, 2.11 reads; under the two-level look none. A design that strands a few keys strands them in many places, and an absent lookup cannot know in advance which regions are clean.

A real table’s lookups are a mixture, and the mixture decides the design. A store serving mostly hits wants the cheapest insertion; one serving mostly misses, a membership test in front of a slower store, wants the deepest search it can afford. A tag that answers more than yes and the filter essays before it found the same division along a different line: what a structure is for decides which of its costs counts.

Every load, every mix

Across four loads and seven mixes, blind kicks at a cap of two are the cheapest table in 23 of 28 settings when stored keys are asked for evenly and in 28 when they are asked for by recency; only for absent keys does a deeper search win, from about ten lookups an insertionThe cheapest of the seven designs at each load (90%, 95%, 97%, 98%) and mix (0.1, 0.3, 1, 3, 10, 30, 100 lookups an insertion), counting an insertion's reads and its lookups', for lookups of stored keys asked for evenly, of stored keys asked for by recency, and of absent keys. stored keys asked for evenly: 90% blind2/blind2/blind2/blind2/blind2/deep/emptiest2; 95% blind2/blind2/blind2/blind2/blind2/blind2/first2; 97% blind2/blind2/blind2/blind2/blind2/blind2/first2; 98% blind2/blind2/blind2/blind2/blind2/blind2/first0. stored keys asked for by recency: 90% blind2/blind2/blind2/blind2/blind2/blind2/blind2; 95% blind2/blind2/blind2/blind2/blind2/blind2/blind2; 97% blind2/blind2/blind2/blind2/blind2/blind2/blind2; 98% blind2/blind2/blind2/blind2/blind2/blind2/blind2. absent keys: 90% blind2/blind2/blind32/blind32/blind32/blind32/blind32; 95% blind2/blind2/blind2/blind32/deep/deep/deep; 97% blind2/blind2/blind2/blind2/deep/deep/deep; 98% blind2/blind2/blind2/blind2/deep/deep/deep.0.10.3131030100lookups for every insertionstored keys asked for evenly90% loadblind 2blind 2blind 2blind 2blind 2two-levelemptiest95% loadblind 2blind 2blind 2blind 2blind 2blind 2look 297% loadblind 2blind 2blind 2blind 2blind 2blind 2look 298% loadblind 2blind 2blind 2blind 2blind 2blind 2look 0stored keys asked for by recency90% loadblind 2blind 2blind 2blind 2blind 2blind 2blind 295% loadblind 2blind 2blind 2blind 2blind 2blind 2blind 297% loadblind 2blind 2blind 2blind 2blind 2blind 2blind 298% loadblind 2blind 2blind 2blind 2blind 2blind 2blind 2absent keys90% loadblind 2blind 2blind 32blind 32blind 32blind 32blind 3295% loadblind 2blind 2blind 2blind 32two-leveltwo-leveltwo-level97% loadblind 2blind 2blind 2blind 2two-leveltwo-leveltwo-level98% loadblind 2blind 2blind 2blind 2two-leveltwo-leveltwo-levelthe cheapest designafter churn, 16,384 entries
Fig. 6 The cheapest of the seven designs at loads of 90%, 95%, 97% and 98% and mixes of a tenth to a hundred lookups an insertion. Stored keys asked for evenly: blind kicks under a cap of two in 23 of 28 settings, the first-found look or the two-level look or the emptiest-bucket look in the rest, all at thirty lookups an insertion or more. By recency: blind kicks under a cap of two in all 28. Absent keys: blind kicks under a cap of two up to one lookup an insertion, then blind kicks under a cap of 32 or the two-level look.

Across four loads and seven mixes, blind kicks under a cap of two are the cheapest table in 23 of 28 settings when stored keys are asked for evenly, in all 28 when they are asked for by recency, and in only the lightest mixes when the keys asked for are absent. The five exceptions for stored keys all lie at thirty lookups an insertion or more, where the looks’ slightly cheaper lookups finally repay their dearer insertions.

That reverses the earlier essays’ ranking for most workloads. On their two numbers, overflow and insertion reads, blind kicks under a cap of two were the worst design measured, and the two-level look the best. With lookups charged for where keys actually live, the design that left 9% of its keys in overflow is the cheapest for every workload that asks for keys the table holds, until it is read thirty times for every write.

What the prediction got right

The closing section predicted that at one lookup an insertion the first-found look would be cheapest at 95% load, and that by ten lookups an insertion the two-level look would be cheapest at every load from 95% up. At one lookup an insertion blind kicks under a cap of two are cheapest, by more than two reads. By ten lookups an insertion the two-level look is cheapest only when the keys looked up are absent; for keys the table holds, blind kicks stay cheapest to thirty lookups an insertion, and by recency to a hundred.

It priced overflow at its own cost and missed the cost of the relocations every search for room makes, which put a fifth of the keys in their second buckets under the deeper searches. And it named the risk that overflow would hold the newest keys, which the workloads that favour recent keys would punish. Overflow holds the oldest: a kick strands a resident, never the key being inserted, and the keys a recency-skewed workload asks for are the ones no chain has yet touched. The question was whether a table’s search depth should be set by its load alone or by how often what it stores is asked for. It is set by which keys are asked for, and by whether they are there.

That moves the choice from the table to its workload. The same table at the same load, under the same churn, has a different cheapest search according to whether its lookups find what they look for. A table probed mostly for keys it lacks, such as the table a hash join fills from its smaller input when most probes miss, wants the two-level look; one probed mostly for keys it holds, and most of all for its recent ones, wants the blind kicks and their stranded old keys.

What the lookups assume

The first bucket first. A lookup reads its first bucket and then its second, which is what makes two probes two misses whenever the second is needed. A table that read both at once, as a cache-line pair, would charge a key in its second bucket the same as one in its first, and the relocations would cost nothing; then the overflow’s price is all that is left, and the deeper searches would win sooner.

Two popularities. Lookups drawn evenly over the stored keys, or by recency with weights 1/(r+1)1/(r+1). A workload skewed toward some keys for reasons unrelated to their age would see the even case’s numbers.

Deletions at random. The churn deletes a key chosen evenly, so an old key is as likely to leave as a new one. A workload that deleted its oldest keys first, as a cache evicting by age does, would empty its overflow blocks by itself, since the keys there are the old ones, and the age profile here would be cut off at its far end.

One table. The earlier essays’ table and churn, one seed a design; the costs compared are differences of a few hundredths of a read a lookup, and the orderings hold at every load.

Still open: a kick that moves the oldest resident

The kick picks a resident at random, and the plates show what that does over time: every key drifts outward with age, and the keys a workload asks for most are the ones that have drifted least. A kick could instead choose which resident to move, and the choice that suits a recency-skewed workload is the oldest resident, the one least likely to be asked for, which a bucket can find from an age kept beside each entry or approximated by its position in the bucket.

The measurement that follows gives the blind kick that choice under every cap, and the first-found look a preference for the oldest resident among those with room, and charges the same lookups. The prediction is that under recency the mean lookup falls from 1.09 reads to about 1.04 at 95% load, since the newest half of the keys would almost never leave their first buckets, and that under even lookups nothing changes, since an even workload does not care which keys move. It could fail on the chains: moving the oldest resident sends it to its other bucket, where it displaces another, and if the oldest keys’ other buckets are themselves full of old keys, chains may lengthen and insertions cost more than the lookups save. The question is whether a table can choose which of its keys pay for the room, or only how much room it pays for.

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.

Cuckoo hashingDesign parameterHash tableLoad factorMeasured countWorkload