When the algorithm flips a coin

Positions confined to one line

A Bloom filter lookup reads 5.55 cache lines because its six positions are scattered across the whole filter. Confining them to a 512-bit block makes it exactly one, and costs 7% more false positives at eight bits a key. At sixteen bits a key the same block costs 91%, and the two-value trick that is free across a whole filter costs another 135% inside one — because a block is a small filter, and small filters are where the penalty lives.

Two hash values and the keys they copy measured what it costs a Bloom filter to make its kk positions from two hash values as h1+ih2h_1 + i\,h_2 rather than from kk independent ones. The penalty is real and it is a finite-size effect: 2.6 times the independent rate on a filter of 64 bits, 1.13 times at k=7k = 7 on 1,024 bits, and nothing worth measuring on a filter of any practical size. It ended by observing that there is a common design which makes every filter small from a key’s point of view.

A blocked Bloom filter uses a first hash to choose one block — 512 bits, a cache line’s worth — and puts all kk positions inside it. A lookup then reads one line instead of kk scattered words, which is the same locality argument the bucket that fits a line made for a hash table’s probe sequence. And the penalty a key’s copies cause is now counted against 512 bits rather than against the whole filter, so a construction that was free everywhere becomes a question again.

This page measures the block’s own cost, the two-value penalty inside it, and whether the cubic repair still works there.

What a block buys, in lines

The quantity a block exists to reduce is easy to count and rarely counted.

A lookup reads 5.55 cache lines across the whole filter and exactly one inside a block, and a block of 512 bits costs 7% more false positives for itDistinct 512-bit lines a lookup touches against the false-positive rate, for 6 independent positions in a filter of 16,384 bits holding 2,048 keys — 8 bits a key — with the positions confined to blocks of the stated size. the whole filter: 5.55 lines at 2.171%. blocks of 64: 1.00 lines at 3.791%. blocks of 128: 1.00 lines at 2.992%. blocks of 256: 1.00 lines at 2.582%. blocks of 512: 1.00 lines at 2.329%. blocks of 1024: 1.97 lines at 2.272%. blocks of 2048: 3.29 lines at 2.195%.0%1.1%2.1%3.2%4.2%the whole filterblocks of 64blocks of 5120123456distinct 512-bit lines a lookup readsabsent keys answered yes16,384 bits, 2,048 keys, k = 6one line is what a block buys
Fig. 1 Distinct 512-bit lines a lookup touches, against the false-positive rate, for six positions a key in a filter of 16,384 bits holding 2,048 keys. Across the whole filter a lookup reads 5.55 lines at a rate of 2.171%. Confined to a block of 512 bits it reads exactly one, at 2.329%. Blocks of 64, 128 and 256 bits also read one, at 3.791%, 2.992% and 2.582%. Blocks of 1,024 and 2,048 bits read 1.97 and 3.29 lines, because a block wider than a line is not one line.

5.55 rather than six, because six positions drawn from thirty-two lines occasionally repeat one. The blocked filter reads one, by construction, and the two points at the right of the plate say what “by construction” means: a block of 1,024 bits spans two lines and reads 1.97 of them, and a block of 2,048 spans four and reads 3.29. The largest block that guarantees one line is one line, and every block smaller than that reads one line too and costs more rate. So the design has exactly one sensible setting for the block, and the rest of this page is about what that setting costs.

The rate paid for it is 2.329% against 2.171% — seven per cent more false positives in exchange for a fifth of the memory traffic. Whether that is a good trade is not a question this page can settle, because it compares a rate with a count of cache lines and nothing here converts one into the other. What it can do is say how the seven per cent behaves, because it is not a constant.

It is also worth being concrete about what the two quantities buy, because they are paid by different parts of a system. The lines are paid on every lookup, positive or negative. The rate is paid only on a negative that answers yes, and what it costs then is whatever the filter was standing in front of: the filter each run carries measured a log-structured store where a false positive is a block read from a level that does not hold the key, and the filter that feeds the table one where it is a wasted probe into a hash table. A false positive against a disk read is worth thousands of cache lines; a false positive against an in-memory probe is worth one or two. So the same 0.158 percentage points is a good trade in one system and a bad one in the next, and a filter’s own plates cannot say which — which is why this page reports the two costs separately rather than combining them into a figure of merit nobody could use.

The block’s own cost, against the block’s size

Confining every key's positions to a block of 512 bits costs 7% on its own, and the two-value scheme adds 9% on top of thatFalse-positive rate against block size, on logarithmic axes, for a filter of 16,384 bits holding 2,048 keys at 8 bits a key with 6 positions a key, under three ways of making the positions inside the block. The flat lines are the same schemes across the whole filter. Blocks of 64 bits (8 keys a block): k independent hashes 3.791%, h₁ + i·h₂, step odd 4.717%, h₁ + i·h₂ + (i³ − i)/6 3.893%. Blocks of 128 bits (16 keys a block): k independent hashes 2.992%, h₁ + i·h₂, step odd 3.526%, h₁ + i·h₂ + (i³ − i)/6 3.092%. Blocks of 256 bits (32 keys a block): k independent hashes 2.582%, h₁ + i·h₂, step odd 2.842%, h₁ + i·h₂ + (i³ − i)/6 2.587%. Blocks of 512 bits (64 keys a block): k independent hashes 2.329%, h₁ + i·h₂, step odd 2.546%, h₁ + i·h₂ + (i³ − i)/6 2.392%. Blocks of 1024 bits (128 keys a block): k independent hashes 2.272%, h₁ + i·h₂, step odd 2.330%, h₁ + i·h₂ + (i³ − i)/6 2.259%. Blocks of 2048 bits (256 keys a block): k independent hashes 2.195%, h₁ + i·h₂, step odd 2.230%, h₁ + i·h₂ + (i³ − i)/6 2.188%. Across the whole filter: k independent hashes 2.171%, h₁ + i·h₂, step odd 2.132%, h₁ + i·h₂ + (i³ − i)/6 2.184%.6412825651210242048block size, bitsabsent keys answered yesk independent hashesh₁ + i·h₂, step oddh₁ + i·h₂ + (i³ − i)/616,384 bits, 2,048 keys, k = 6dashed: the same scheme across the whole filter
Fig. 2 False-positive rate against block size, on logarithmic axes, for the same 16,384-bit filter holding 2,048 keys with six positions a key, under three ways of making the positions inside the block; the flat dashed lines are the same three schemes across the whole filter. With independent positions: 3.791% in blocks of 64 bits, 2.992% at 128, 2.582% at 256, 2.329% at 512, 2.272% at 1,024 and 2.195% at 2,048, against 2.171% for the whole filter.

The block’s cost falls steadily as the block grows and is already small at 512 bits. The mechanism is the one a reader may guess and it is worth stating exactly, because it is the only thing on this page that is not about hashing.

A whole filter of 16,384 bits holding 2,048 keys holds exactly 2,048 keys. A filter divided into thirty-two blocks holds, in each block, however many of the 2,048 keys happened to choose it.

The keys divide themselves 47 to 77 across 32 blocks that each expect 64, and the 13 blocks above the mean cost more than the 19 below it saveHow many of 2,048 keys landed in each of the 32 blocks of 512 bits in one filter of 16,384 bits, with the even division a whole filter gets drawn as a line at 64. The counts run from 47 to 77, a standard deviation of 7.3 against the 8.0 a Poisson count of this mean would give. A block over its share is a filter over its design load, and the false-positive rate is convex in the load, so the blocks above cost more than the blocks below save.04285each of the 32 blockskeys that chose itan even division: 64dark: blocks over theirshare16,384 bits, 2,048 keysa block over its share is a filter over its load
Fig. 3 How many of 2,048 keys landed in each of the thirty-two 512-bit blocks of one filter, against the even division of 64 a whole filter gets for nothing. The counts run from 47 to 77, a standard deviation of 7.3 against the 8.0 a Poisson count of this mean would give. Thirteen blocks are above their share and nineteen below.

A block that received 77 keys is a filter twenty per cent over its design load, and the false-positive rate is steeply convex in the load — at eight bits a key and six positions, twenty per cent more keys is about forty per cent more false positives. The nineteen blocks below the mean save less than the thirteen above cost, and the difference is the whole of a block’s penalty. It shrinks as the blocks grow because the relative spread of a count like this falls as its mean rises: at 2,048-bit blocks the mean is 256 and the spread about sixteen, six per cent rather than twelve, and the measured penalty is 1% rather than 7%.

Nothing about hashing is involved in that, which is worth being clear about before the next section, where everything is.

That also predicts where the penalty is largest, and the prediction is checkable.

A 512-bit block costs 0% at 4 bits a key and 91% at 16, because a tighter filter has more to lose from an uneven blockFalse-positive rate against bits a key, on logarithmic axes, for 2,048 keys in one filter, in the same filter with each key's positions confined to a 512-bit block, and in the blocked filter with the positions made from two hash values. The number of positions is set to the optimum for each density. 4 bits a key (k = 3, 128 keys a block): whole 14.719%, blocked 14.783%, blocked with two values 14.769%. 6 bits a key (k = 4, 85 keys a block): whole 5.506%, blocked 5.780%, blocked with two values 5.875%. 8 bits a key (k = 6, 64 keys a block): whole 2.171%, blocked 2.329%, blocked with two values 2.546%. 10 bits a key (k = 7, 51 keys a block): whole 0.831%, blocked 0.986%, blocked with two values 1.136%. 12 bits a key (k = 8, 43 keys a block): whole 0.311%, blocked 0.418%, blocked with two values 0.570%. 14 bits a key (k = 10, 37 keys a block): whole 0.103%, blocked 0.191%, blocked with two values 0.362%. 16 bits a key (k = 11, 32 keys a block): whole 0.049%, blocked 0.093%, blocked with two values 0.219%.468101214160.0010.010.1bits a keyabsent keys answered yesthe whole filterblocks of 512 bitsblocks, two hash values2,048 keys, k at its optimumone line read either way, for the blocked pair
Fig. 4 False-positive rate against bits a key, on logarithmic axes, for 2,048 keys in one filter, in the same filter blocked at 512 bits, and blocked with the positions made from two hash values; the number of positions is set to the optimum at each density. At four bits a key: 14.719% whole, 14.783% blocked. At eight: 2.171% and 2.329%. At twelve: 0.311% and 0.418%. At sixteen: 0.049% and 0.093%.

At four bits a key a 512-bit block costs nothing measurable. At sixteen it costs 91%, nearly doubling the rate. The reason is the convexity again: a rate of 14.7% cannot rise much, and a rate of 0.049% has almost everywhere to go. A blocked filter is close to free when it is loose and expensive when it is tight, which is the opposite of what a designer choosing “one cache line per lookup” would expect from a decision that looks purely architectural.

That is the first practical finding and it inverts the usual reasoning. Blocking is sold as a locality optimisation with a small constant penalty. It is a locality optimisation whose penalty is set by how precise the filter is being asked to be, and the filters where precision matters most are the ones where blocking costs most.

Two hash values, inside a block

Now the question the earlier page left. Across a filter of 16,384 bits the three position schemes are indistinguishable: 2.171% from independent hashes, 2.132% with an odd-stepped h1+ih2h_1 + i\,h_2, 2.184% with the cubic term. Inside a block they are not.

The two-value penalty inside a block is 5.6n over the block's bits squared at every block size — the shape the earlier account gives, at 2.1 times less than its constantThe measured penalty for making a block's positions from two hash values instead of 6 independent ones, divided by the keys in a block over the square of the block's bits, at 4 block sizes in a filter of 16,384 bits holding 2,048 keys. A flat line says the penalty is proportional to that quantity; its height is the constant. Blocks of 64 bits with 8 keys each: 3.726% independent, 4.739% from two values, a penalty of 1.014 points and a coefficient of 5.19. Blocks of 128 bits with 16 keys each: 2.979% independent, 3.513% from two values, a penalty of 0.533 points and a coefficient of 5.46. Blocks of 256 bits with 32 keys each: 2.550% independent, 2.841% from two values, a penalty of 0.291 points and a coefficient of 5.96. Blocks of 512 bits with 64 keys each: 2.353% independent, 2.496% from two values, a penalty of 0.143 points and a coefficient of 5.84. The account that counts copied and near-copied keys gives 12; counting only the near-copies whose remaining bits are also set, which at the optimal number of positions is half of them, gives about 6.03691264128256512block size, bitspenalty ÷ (keys in a block ÷ block bits squared)the account, as writtenmeasuredthe account, near-copiesweighted16,384 bits, 2,048 keys, 96 filtersflat: the shape is right; height: the constant is not
Fig. 5 The measured penalty for making a block’s positions from two hash values rather than six independent ones, divided by the keys in a block over the square of the block’s bits, at four block sizes. The quotient reads 5.19, 5.46, 5.96 and 5.84 — flat, so the penalty is proportional to that quantity, which is the shape the earlier account gives. Its height is not: the account as written gives 12, and counting only the near-copies whose remaining bits are also set gives about 6.

The shape transfers exactly. The earlier page’s account says the penalty comes from a query whose start-and-step pair copies a stored key’s whole progression, or copies it in all but a few positions, and that the number of such pairs over the number of pairs available gives a penalty of about 12n/m212n/m^2. Evaluated with the block’s bits and the block’s keys, that account predicts the penalty halving at every doubling of the block — and it does, four times over, which is what a flat quotient means.

The constant does not transfer, and it is out by a factor of about 2.1 in a direction the account itself explains. Its twelve counts two pairs that copy a stored key exactly and four more for each number of positions missed. A query that copies a stored key in all but one position is a false positive only if that remaining position happens to be set by some other key — and at the optimal number of positions a Bloom filter’s bits are set half the time, by construction. Weighting the near-copies by the chance their remaining bits are set turns 12 into about 6, and the measurement reads 5.6. The earlier page found the same account overstating by about a fifth on a whole small filter and said so; inside a block, where the near-copy terms matter more, it overstates by rather more.

What this costs a real filter is on the density plate above. At eight bits a key the two-value scheme adds 9% on top of the block’s 7%; at sixteen bits a key it adds 135% on top of the block’s 91%, taking a filter that would have answered 0.049% of absent keys yes to one that answers 0.219%. A blocked filter at high precision is exactly the case the earlier page’s penalty was measured on, because a 512-bit block is a small filter, and the construction that is free across a filter of any size is not free inside one of its blocks.

Whether the cubic term still repairs it

It does, and mostly.

Across the block sizes on the second plate, the cubic term — positions at h1+ih2+(i3i)/6h_1 + i\,h_2 + (i^3 - i)/6 — recovers most of the two-value penalty at every size. In blocks of 64 bits it takes 4.717% back to 3.893% against 3.791% for independent hashes, closing 89% of the gap. At 512 bits it takes 2.546% back to 2.392% against 2.329%, closing 71%. At 1,024 and 2,048 bits it lands slightly below the independent scheme, which is within the measurement’s spread rather than a finding.

The false-positive rate as a multiple of independent hashing, k = 7, against filter size: h₁ + i·h₂ 3.81×, h₁ + i·h₂, step odd 2.61×, h₁ + i·h₂ + (i³ − i)/6 1.19× at 64 bitsFilters of 64 to 1,024 bits with k = 7, each holding the optimal load of m·ln 2 / k keys. Independent hashing measures 0.68% at 64, 0.92% at 128, 0.76% at 256, 0.80% at 512, 0.78% at 1,024. h₁ + i·h₂: 3.81×, 2.26×, 1.65×, 1.35×, 1.18×. h₁ + i·h₂, step odd: 2.61×, 1.74×, 1.34×, 1.23×, 1.10×. h₁ + i·h₂ + (i³ − i)/6: 1.19×, 1.11×, 1.04×, 1.06×, 1.00×. Dashed: 1 + 12n/m² over the independent rate for the odd step, and 1 + n/m² over it for the cubic term.641282565121,02411.523bits in the filter, mrate ÷ the independent rateh₁ + i·h₂h₁ + i·h₂, step oddh₁ + i·h₂ + (i³ − i)/6optimal load m·ln 2 / k; one set of hash functions per schemedashed: the account
Fig. 6 The same three schemes measured on whole filters of the block sizes above, each at the load that makes seven positions optimal, as a multiple of the independent rate. At 64 bits: 3.81× for an unforced step, 2.61× with the step forced odd, 1.19× with the cubic term. At 512 bits: 1.35×, 1.23× and 1.06×. The dashed curves are the account, 1+12n/m21 + 12n/m^2 over the independent rate for the odd step and 1+n/m21 + n/m^2 for the cubic term.

Setting that plate beside the block sweep answers a question it would be easy to get wrong. A whole filter of 512 bits pays 1.23× for an odd-stepped two-value scheme; a 512-bit block pays 1.061×. The block looks kinder and is not. It holds 64 keys where the standalone filter holds 50.7, so its absolute penalty is larger — 0.143 points against 0.18 — and the multiplier is smaller only because the block’s base rate is 2.35% where the standalone filter’s is 0.80%. A multiplier is a statement about its denominator, and the denominator here is how precise the filter was being asked to be, which is the same variable the block’s own penalty turned on.

So the practical arrangement is unchanged by blocking: two hash values, a cubic correction, and the penalty is a few per cent rather than tens of per cent. The cost of the correction is two multiplications and a division by six per position, which is nothing beside a cache miss — and the whole reason for a blocked filter is that it has fewer cache misses to hide behind.

What a designer chooses, and in what order

The plates above have three settings in them and they are not independent, so the order of the decisions matters.

The block size is not a free parameter. The hero plate says a block smaller than a cache line reads one line and costs more, and a block larger than one reads more than one line. So a blocked filter’s block is a cache line, always, and there is nothing to sweep. Every other page on these filters has found the opposite — the threshold somebody chose swept four constants written into real code and found none of them at an optimum — and it is worth noticing when a constant genuinely has only one sensible value.

The bits a key is where the decision actually is. Blocking costs nothing at four bits a key and nearly doubles the rate at sixteen, so the question “should this filter be blocked” cannot be answered without the target rate. A filter at 1% is blocked for free; a filter at 0.05% is not, and a designer who blocks it has quietly moved it to 0.09% — still a small number, and twice what was asked for.

And the two-value construction is decided after both. Its penalty inside a block is 5.6n/m25.6n/m^2, which at 512 bits and 64 keys is a seventh of a percentage point and at sixteen bits a key is more than the block’s own cost. The cubic term removes most of it for two multiplications, so the honest default is to use it and stop thinking about the question — which is the conclusion the earlier page reached about whole filters and which blocking does not overturn.

The reason this ordering is worth writing down is that all three decisions look architectural and only one of them is. The block size is a fact about the machine. The bits a key is a fact about what the caller will accept. And the position scheme is a fact about the hash, which is where a reader would expect the whole page to be and where almost none of it turned out to be.

What this says about where a filter’s rate comes from

Three of the plates on this page are the same fact in three coordinates, and it is worth naming because it governs every filter design measured here.

A filter’s false-positive rate is set by its bits per key and its load, and both of those are properties of the smallest independently-addressed piece of it. A whole filter’s mean load is not what decides its rate if its keys are divided into blocks that each have their own; a fingerprint table’s mean fingerprint length is not what decides its life if its generations have their own, which is what the bits given to the wrong keys measured; and a stack of filters has a rate that is the sum of its layers’ rather than the rate of any of them.

The general form is that dividing a structure into pieces makes the pieces’ variation part of its cost, and a design that divides for locality is paying that cost whether or not it is measured. Seven per cent at eight bits a key is a price worth paying for a fifth of the memory traffic. Ninety-one per cent at sixteen bits a key is a different decision, and it is made by the same line of code.

What is not measured here

Cache lines counted, not cache misses. The hero plate counts distinct 512-bit lines a lookup’s positions fall in. A real lookup misses on however many of those are not already resident, which depends on the filter’s size against the cache, on what else is running, and on the prefetcher. The block that is not a block is the argument that this model is a model; the count here is the best case for the scattered filter and the exact case for the blocked one, so the gap measured is a lower bound on the gap in misses.

One filter size. Every measurement holds 2,048 keys. The block penalty depends on the keys per block rather than on the filter’s size, so a larger filter at the same bits a key and the same block size should show the same penalty; that is an argument rather than a sweep.

No SIMD. The practical argument for a blocked filter includes setting and testing all kk bits of a block with vector instructions, which changes the time and not the rate. Nothing here is timed.

One number of positions per density. The load plate sets kk to the optimum for each bits-a-key, which is the right comparison for a designer and the wrong one for isolating a mechanism: the block penalty and the kk both move between points. A sweep at fixed kk would separate them and is not drawn. The threshold somebody chose is the general warning — a plate that moves two settings at once reports their sum.

Blocks chosen uniformly. The first hash spreads keys over blocks as evenly as a random assignment does, which is what makes the block’s penalty a balls-in-bins effect. A filter whose keys arrive with structure the block hash does not destroy would be worse, and by an amount no plate here bounds.

One correction, at one load. The cubic term is the only repair measured, and the weighting that turns twelve into six assumes the filter is at the load where half its bits are set. A filter deliberately run under its optimal load sets fewer, and the account’s constant would move.

Still open: a block that is chosen twice

Every key on this page chooses its block once, and a block that receives more keys than its share stays overloaded for the filter’s life. That is a balls-in-bins problem with a standard repair, and the repair has already been measured in another structure: the second choice found that offering each key two buckets and taking the emptier one lowers the maximum load from a logarithm to a logarithm of a logarithm.

A filter cannot quite do that, because a lookup must know which block to read without knowing how full either was. But it can do half of it: choose two candidate blocks, insert into the emptier, and at lookup read both. That costs a second line on every lookup — half the saving the block was for — and should flatten the block-occupancy spread that this page’s whole penalty comes from.

The measurement that follows builds that filter at each density on the load plate and asks three things: how much of the block penalty the second choice removes, whether what it removes is worth reading a second line at eight bits a key and at sixteen, and whether a rule that reads the second block only when the first says no — which is free on a true negative and costs nothing on a true positive — keeps the rate of reading both while costing closer to one.

What this makes readable

Essays that name this one as a prerequisite.

Named alongside this one

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

What links here

Every essay whose body links to this one.

The objects this essay names

Each one links to every other essay that touches it.

Approximate membershipBalls in binsBlocked filterBloom filterCacheDesign parameterDouble hashingFalse-positive rateHash functionLocalitySpace time trade