One pass, and no room

The bits that say when

A windowed cardinality estimator holds 4,592 bits and 2,392 of them are clocks. Every summary in this collection has reported its size from the shape of its own structure, and not one of those numbers has ever been asked what the bits were for — so the resource that half of these structures spend most of their state on has been invisible while being counted.

Build a cardinality estimator that answers about the last four thousand arrivals rather than about the whole stream, give it two hundred and fifty-six registers, and weigh it. It holds 4,592 bits.

Ask what those bits are for and the answer is: 920 of them are the estimate, 1,280 are a directory, and 2,392 are clocks.

More than half the structure is spent on knowing when things happened. Not on the answer, not on the keys, not on the hash — on timestamps, which are the one part of a windowed structure that nothing in the accuracy parameter touches and nothing in the published analysis mentions by name.

Where a windowed structure's bits go — 30,000 arrivals, W = 4,0964 structures over the same stream, each bar the whole of its state and split into what the bits are for. Only the two rightmost bands are the answer; the rest is knowing which item and knowing when. windowed HyperLogLog holds 4,088 bits, 50% of it stamps; one stamp per live key holds 8,775 bits, 29% of it stamps; blocks of Misra-Gries holds 18,549 bits, 1% of it stamps; the last W keys, kept holds 131,072 bits and keeps no clock. The parts are computed from each structure's own shape and are required to sum to the total it reports, so a band that had been left out of the accounting would stop the build rather than make a bar look short.windowed HyperLogLog4,088 bitsone stamp per live key8,775 bitsblocks of Misra-Gries18,549 bitsthe last W keys, kept131,072 bitskeysstampspayloadindexthe parts, in the order they stackwindow 4,096 · the popular keys drift131,072 bits at most
Fig. 1 Four structures over the same thirty thousand arrivals, each answering a question about the last 4,096 of them, each bar the whole of its state split into what the bits are for. Only the leftmost band on each bar is the answer. The parts are computed from each structure’s own shape and are required to sum to the total it reports, so a component left out of the accounting stops the build rather than making a bar look short.

What was being counted, and what was not

Every summary in this collection reports bits(), and every one of those numbers is computed from the shape of the structure rather than measured off a language runtime’s objects — a hash map of eight entries does not occupy the bits reported for a Misra-Gries table with eight counters and never could. That convention has held since the field opened, and it is the right one.

What it cannot do is show what the bits are for. A total of 4,592 is consistent with any story about where they went, and the story turns out to be the interesting part.

So each structure here answers a second question. stateParts() returns named components; the total is required to equal them summed, checked on every call and on every build; and the names are fixed across the family so that a reader learns them once:

  • payload — bits spent on the answer. Registers, counters, stored values, codes.
  • stamps — bits spent on knowing when, and on nothing else.
  • keys — bits spent on knowing which item. Kept apart from the payload because a counter-based structure is mostly keys and a register-based one has none at all.
  • directory — lengths, offsets, bucket counts: the bookkeeping that is neither the answer nor the time.

An earlier structure in this collection already paid the third of these and did not say so. The exponential histogram from the summary that has to forget stores a timestamp per bucket and adds it into its reported size beside the bucket’s exponent. The arithmetic was correct and the finding was hidden, because a total cannot show that one of its terms is the majority of it.

An exponential histogram over a window of 400: 29 buckets, 200 against 200Each box is one bucket: its label is how many ones it covers, always a power of two, and its position is the span of arrivals it summarises. At most 6 buckets of any one size are allowed, and the 1,764 merges so far are what has kept that true. The estimate counts every bucket in full except the oldest, which is counted half — the shaded one, which straddles the edge of the window with part of it already outside and nothing recorded to say how much. That single bucket is the entire error of the structure: here it is 32 of 215, so the estimate is out by at most 16 against a true 200, and it is out by 1. The structure holds 406 bits against the 400 the window itself would take.3,600 — the window opens4,000 — now32161616884the window edge215 in buckets−16 for the oldest= 200true 2000.3% outsliding-window model · ε = 0.2, k = 5 · 1,764 merges406 bits against 400
Fig. 2 The structure that was already paying. Every box is a bucket and every bucket carries the timestamp of its most recent arrival — which is the only thing that lets the leftmost one be recognised as half outside the window. The estimate, the error and the merging are all about the sizes; the mechanism that makes any of it work is the stamps.

A stamp costs the logarithm of the window, plus one

The width is not a free choice and it is not thirty-two bits either.

A structure in this model expires an entry by comparing its stamp against tWt - W. For that comparison to be right the stamps have to stay in order across a wrap, and the smallest counter that does is one modulo 2W2W: log22W\lceil \log_2 2W \rceil bits. Charging log2W\lceil \log_2 W \rceil instead would be charging for a structure that expires the wrong arrivals once per window.

What a timestamp costs, against the length of the windowThe one parameter that moves the clock. A structure in the sliding-window model compares a stored stamp against t − W to decide whether an arrival has left, so its stamps must stay ordered across a wrap — which takes ⌈log₂ 2W⌉ bits, one more than the window itself. From W = 64 to W = 65,536 that is 7 bits rising to 17: a thousandfold longer window for 10 more bits per stamp. It is the accuracy dial that does not touch this quantity, and the window length that does, and the growth is slow enough that the clock is a fixed overhead in every practical setting rather than something to tune.7649256111,024134,0961516,3841765,536window length W, in arrivalsbits per stamp⌈log₂ 2W⌉one bit morethan the windowsliding-window model · a stamp orders arrivals across one wrapcomputed, not measured
Fig. 3 The one parameter that moves the clock, and it moves it slowly. A window a thousand times longer costs ten more bits per stamp. This is the whole of the dependence, it is computed rather than measured, and it is the reason a timestamp is a fixed overhead rather than something to tune.

Seven bits at a window of sixty-four, thirteen at four thousand, seventeen at sixty-five thousand. Against a HyperLogLog register of five bits, that is between one and a half and three and a half times the thing it is attached to, at every window length anybody uses.

A real system storing an epoch millisecond in sixty-four bits is spending far more than this, and saying so would be a different essay. What is charged here is what distinguishing the arrivals in a window costs, which is the smallest honest number and the one a bound would be stated in.

The accuracy dial does not trade against the clock

The interesting property is not that the stamps are large. It is that there is no setting at which they can be made smaller.

Buying accuracy buys clock: the state of a windowed HyperLogLog at 5 register countsEach bar is the whole structure over the same 30,000 arrivals of the popular keys drift, so old keys are gone rather than rare, with a window of 4,096, split into what it is made of. The ρ values — the part that is the answer, and the whole of a cash-register HyperLogLog — are 330 bits at m = 32 and 845 at m = 512. The stamps beside them are 858 and 2197: 2.6 times the payload at every setting, because a ρ value is 5 bits wide and a stamp is 13. Nothing here trades one against the other — turning the accuracy dial raises both bands together, so there is no setting of this structure at which knowing the answer better is paid for by knowing the time worse.1,348m = 322,156m = 643,034m = 1284,088m = 2565,602m = 512registersbits of statestampspayloaddirectorystamps ÷ payload = 2.60at every register countwindow 4,096 · the popular keys driftstamps 13 bits, ρ 5 bits
Fig. 4 The same structure at four register counts, which is its accuracy dial. Both bands rise together. The ratio between them is 2.6 at every setting, because a ρ value is five bits wide and a stamp is thirteen, and the ratio is a property of the widths rather than of the accuracy.
registers ρ values stamps directory total stamps’ share
32 345 897 160 1,402 64.0%
64 540 1,404 320 2,264 62.0%
128 755 1,963 640 3,358 58.5%
256 920 2,392 1,280 4,592 52.1%

Raise the register count and the answer gets better; the payload grows, and the stamps grow with it, in exact proportion, because both are charged per live entry. There is no configuration of this structure in which knowing the answer better is paid for by knowing the time worse.

The share does drift downwards across the sweep, from 64% to 52%, and the reason is the third band rather than the second: the directory is a length per register whether that register holds anything or not, so it grows linearly in mm while the entries do not. That is a real effect and it is not the clock getting cheaper. Both assertions are made on every build — that the stamps exceed the payload at every setting, and that raising the payload never lowers the stamps — and the second is the one that could fail.

There is one more thing the second assertion protects, and it is the reason it is written as a comparison between consecutive settings rather than as a claim about the ratio. The ratio between the stamps and the payload is 2.6 at every register count for a trivial reason — thirteen over five, fixed by the two widths — so asserting that would be asserting arithmetic. What is not arithmetic is that both quantities are charged per live entry, and a structure that had found some way to keep fewer clocks than candidates would break it.

Three ways to pay, and each structure picks one

Four structures on the opening plate, answering questions about the same window, and the accounting separates them more sharply than their sizes do.

The ring buffer pays keys and nothing else. Keeping the last WW arrivals is the structure with no cleverness in it, and it needs no clocks at all: position in the buffer is the time. 131,072 bits, every one of them a key, and it is the only structure here whose stamps are genuinely zero rather than small.

A stamp per live key pays both, roughly two to one. The smallest exact distinct-counter is a map from each key to the position of its most recent arrival — nothing older matters, because a key is in the window exactly when its last arrival is. 8,775 bits: 6,240 of keys and 2,535 of stamps, 29% of the structure. This is the honest denominator for anything the approximate structures claim.

A block of summaries pays almost no stamps at all. Cut the window into eight blocks, give each its own Misra-Gries table, and drop a block whole when its last arrival leaves. There is one stamp per block rather than per item — 117 bits of the 18,549 — and what it buys is coarseness: the answer is about between WW and W+W/8W + W/8 arrivals and the structure cannot say which. The clock got cheap by being consulted eight times instead of four thousand.

And the register-list pays more clock than answer. 4,088 bits, half of them stamps.

Where a windowed structure's bits go — 30,000 arrivals, W = 4,0964 structures over the same stream, each bar the whole of its state and split into what the bits are for. Only the two rightmost bands are the answer; the rest is knowing which item and knowing when. windowed HyperLogLog holds 4,088 bits, 50% of it stamps; one stamp per live key holds 8,775 bits, 29% of it stamps; blocks of Misra-Gries holds 18,549 bits, 1% of it stamps; the last W keys, kept holds 131,072 bits and keeps no clock. The parts are computed from each structure's own shape and are required to sum to the total it reports, so a band that had been left out of the accounting would stop the build rather than make a bar look short. The horizontal scale is logarithmic, because the largest bar is more than thirty times the smallest.windowed HyperLogLog4,088 bitsone stamp per live key8,775 bitsblocks of Misra-Gries18,549 bitsthe last W keys, kept131,072 bitskeysstampspayloadindexthe parts, in the order they stackwindow 4,096 · the popular keys drift131,072 bits at most
Fig. 5 The same four bars with their lengths on a logarithmic scale, because the ring buffer is thirty times the next one and a linear plate cannot show the other three. The proportions inside each bar are unchanged; only the lengths are compressed. Read the bands, not the bar lengths.

Why the cardinality estimator is the extreme case

The structure spending most on clocks is the one whose payload is smallest, and that is not a coincidence — it is the whole shape of the thing.

A HyperLogLog register holds one number: the largest ρ\rho any key hashing there has produced, where ρ\rho is the position of the first one-bit. Five bits, because ρ\rho is at most about twenty-five and rarely above ten. That extreme compression of the answer is why the estimator works at all.

Over a window that one number is not available, because the largest ρ\rho may have arrived and left. So the register becomes a list: every arrival that has not yet been made irrelevant by a later one.

One register of a windowed HyperLogLog: 3 arrivals that could still be its maximumRegister 10 of 64, after 40,000 arrivals with a window of 4,096. A cash-register estimator would hold one number here — the largest ρ ever hashed to this bucket — and that number is exactly what a window is not allowed to know, because the arrival that produced it may have left. So the register holds every arrival that has not been made irrelevant by a later one: a pair (ρ, stamp) survives only while no arrival after it has an equal or larger ρ. The list is therefore strictly decreasing in ρ and increasing in time, its front is the window's maximum once the expired entries are dropped, and its length is what the structure pays over the one number. 39,594 pairs have been deleted this way across the whole structure. Every row carries a stamp of 13 bits beside a ρ of 5.ρarrived atageregister 10 of 64 · window 4,096 · now t = 40,000the maximumρ = 3t = 37,0202,980 agoρ = 2t = 37,4062,594 agowaitingρ = 1t = 37,4322,568 agowaiting91 entries across 64 registers1,958 bits in all
Fig. 6 One register, opened up. A pair survives only while no later arrival has an equal or larger ρ — so the list is strictly decreasing in ρ and increasing in time, and its front is the window’s maximum once the expired entries have gone. Each row is five bits of answer and thirteen bits of clock.

Chabchoub and Hébrail introduced this in 2010 and call it a list of future possible maxima. What the accounting adds is where the bits went. The structure that replaced a number with a list did not mostly buy a list — the lists are short, two or three entries on ordinary data — it mostly bought clocks, because each entry needs one and each is nearly three times the width of what it is attached to.

What this does to a comparison

Two structures answering the same question over the same window, one exact and one approximate, and the sizes are 8,775 and 4,088.

Where a windowed structure's bits go — 30,000 arrivals, W = 4,0962 structures over the same stream, each bar the whole of its state and split into what the bits are for. Only the two rightmost bands are the answer; the rest is knowing which item and knowing when. windowed HyperLogLog holds 9,668 bits, 63% of it stamps; one stamp per live key holds 51,165 bits, 29% of it stamps. The parts are computed from each structure's own shape and are required to sum to the total it reports, so a band that had been left out of the accounting would stop the build rather than make a bar look short.windowed HyperLogLog9,668 bitsone stamp per live key51,165 bitskeysstampspayloadindexthe parts, in the order they stackwindow 4,096 · stationary Zipf51,165 bits at most
Fig. 7 Only the two distinct-counters, on a stationary stream this time so the comparison is not helped by the drift. The approximate structure is smaller, and it is smaller by a factor that would be an embarrassment in the cash-register model.

A factor of two, for an answer that is wrong by a few per cent. In the model where nothing expires the same estimator against the same exact structure is a factor of thirty-nine. That collapse is the next essay’s subject, and the accounting above is most of the explanation: the exact structure sheds keys when they leave, and the approximate one gains clocks it did not need.

What a block buys, and what it costs

The block structure is the one that escapes the clock, and it is worth being precise about what it pays instead.

At one block the structure is a Misra-Gries table over the last WW arrivals plus however many have accumulated since the block began — up to 2W2W, so the answer can be about twice the window asked for. At thirty-two blocks the slack is down to 128 arrivals and the state is 16.5 times larger, 8,218 bits against 135,597. The measured error on the three heaviest keys falls from 42% to 5% across that sweep, and the reason is both effects at once: more blocks means less slack and also more counters in total.

A stamp per block is a coarse clock, and coarse is exactly what makes it cheap. That is the general shape and it is worth stating on its own, because it is the design decision every windowed structure makes: how finely does the structure need to know when things happened, and what does it give up by knowing less?

  • A ring buffer knows to the arrival, and pays with the arrivals themselves.
  • A stamp per key knows to the arrival, and pays a clock per key.
  • A register-list knows to the arrival, and pays a clock per candidate maximum.
  • A block knows to the block, and pays with an answer that is about the wrong number of arrivals.

The clock bill is a headcount, not a width

Two facts from the sections above are worth putting together, because together they say where the only available lever is.

The width of a stamp is log22W\lceil\log_2 2W\rceil and there is nothing to be done about it: it is the smallest counter that orders correctly across a wrap, and charging less would be charging for a structure that expires the wrong arrivals. Thirteen bits at a window of four thousand, and seventeen at sixty-five thousand — a range of four bits over a factor of sixteen in window length.

So the clock bill is the number of stamped entries times a number that barely moves. It is a headcount rather than a width, and every design decision that reduces it is a decision about how many things the structure keeps stamped:

  • a ring buffer stamps nothing, and keeps WW arrivals;
  • a stamp per live key stamps one thing per key in the window;
  • a register list stamps one thing per candidate maximum, which is about two per register;
  • a block stamps one thing per block, and there are eight.

Read down that list and the clock costs fall by orders of magnitude while the structures get coarser about time — and the coarseness is what is actually being bought. The block structure’s answer is about somewhere between WW and W+W/8W + W/8 arrivals precisely because it stamped eight things instead of four thousand.

That reframes the accuracy sweep as well. Raising the register count raises the clock bill because it raises the number of live entries, not because anything about time got more expensive. A structure cannot buy a cheaper clock; it can only decide how many clocks to own, and every windowed structure in this field is a different answer to that question wearing different clothes.

The same split, in a field with no clock in it

The division this essay draws — bits that hold the answer against bits that locate it — is not about time, and the strongest evidence for that is that the same accounting has already produced the same finding somewhere with no clock in it at all.

A compressed self-index spends its bits on a transform, a rank directory and a set of sampled positions. The samples are not the answer to anything; they exist so that a row of the sorted rotations can be turned into a place in the text. They are bits that say where, and in a run-length index over a repetitive collection they turn out to be half the structure — with the same signature as the clocks here: they do not shrink when the answer-carrying part does, so making the payload small makes them the majority.

Set the two side by side and the pattern is the same twice:

what the payload holds what the locating bits hold share, at the sizes measured
windowed cardinality a ρ\rho per candidate when it arrived 52%
run-length self-index the transform’s runs where each sampled row sits 50%

Two fields, two structures with nothing in common, and in both the term that dominates is the one that answers which position rather than what value. In both, it is absent from every published statement of the size. And in both, it became the majority precisely because somebody succeeded at compressing the other half.

So the general form is not “clocks are expensive”. It is that a structure has bits that carry its answer and bits that carry an address into whatever the answer is about, that the second kind is governed by the logarithm of the address space and by how many addresses are kept, and that the second kind is what nobody counts. Naming it once per field, as this essay does for time and that one does for position, is the only thing that has ever made it visible.

What is not measured here

A clock that is not a counter. Every stamp above is a position in the stream, and the model is a count-based window: the last WW arrivals. A time-based window — the last thirty seconds — needs a stamp whose width is set by the clock’s resolution and the window’s duration rather than by the arrival rate, and the two coincide only when arrivals are evenly spaced. Nothing here measures that, and the arithmetic would be different rather than merely larger.

The cost of comparing them. Expiry is a comparison per stored entry per arrival in the worst case, and the structures here are counted for the state they hold rather than for the work they do maintaining it. That is the same choice this collection makes everywhere — a count and a size are different resources — but it is worth saying, because a structure that is small and re-scans its whole state on every arrival is not obviously the better one.

Compressed stamps. The entries in a register’s list arrive in increasing order of time, so the differences between consecutive stamps are smaller than the stamps themselves and could be coded as such. That is a real saving and it is not taken here, because taking it would make the reported size a property of an encoding rather than of the structure — the same reason the sizes throughout this collection come from a structure’s shape and never from a serialisation.

And a share is not a ranking. The plates above say what fraction of each structure is clocks, and a reader looking for a recommendation will be tempted to read a low fraction as a good design. It is not one: the ring buffer’s share is zero and it is the largest structure on the plate by a factor of thirty, because it pays for time by keeping every arrival rather than by keeping a clock. A structure spends nothing on stamps either by being coarse about time, like the blocks, or by being exhaustive about data, like the buffer. The share is a description of where a structure’s bits went, and the totals beside it are what a choice would actually be made on.

Buying accuracy buys clock: the state of a windowed HyperLogLog at 4 register countsEach bar is the whole structure over the same 30,000 arrivals of the popular keys drift, so old keys are gone rather than rare, with a window of 16,384, split into what it is made of. The ρ values — the part that is the answer, and the whole of a cash-register HyperLogLog — are 845 bits at m = 64 and 2210 at m = 512. The stamps beside them are 2535 and 6630: 3.0 times the payload at every setting, because a ρ value is 5 bits wide and a stamp is 15. Nothing here trades one against the other — turning the accuracy dial raises both bands together, so there is no setting of this structure at which knowing the answer better is paid for by knowing the time worse.3,700m = 645,600m = 1288,740m = 25611,400m = 512registersbits of statestampspayloaddirectorystamps ÷ payload = 3.00at every register countwindow 16,384 · the popular keys driftstamps 15 bits, ρ 5 bits
Fig. 8 The accuracy sweep again at a window four times longer, where each stamp costs fifteen bits rather than thirteen. Every band moves and the conclusion does not: the clock is still the larger half of every entry, and raising the accuracy still raises both.

The point of naming a resource is that it can then be traded. Four structures here spend between 0% and 64% of themselves on knowing when things happened; before the split existed, all four were simply bits, and the one that spends the most was the one being recommended as the small option.

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.

ExpiryExponential histogramHonest limitMeasurementMemory layoutMisra–GriesResource accountingSliding windowState bitsSummaryTimestampTrade off