What is taught wrongly

Two factors that do not multiply

Eleven times and seventy-eight times against the same baseline, so an index with both should be eight hundred and sixty. It is seventy-eight, and the shortfall is eleven — the first factor, exactly, because the second operation already contains it.

Two savings, measured a strand apart, against the same published baseline, on the same structure.

The compound walk is 11.0×. The interval enumeration is 78.2×.

A reader with both numbers computes 11.0 × 78.2 = 859.7 for an index with both.

The measured figure is 78.2.

Two savings that do not multiply, and the amount by which they do notThe compound walk is 11x and the enumeration is 78x against the same baseline, so a reader with both numbers computes 860x for an index that has both. The measured figure is 78x, and the shortfall is 11x — which is the compound walk's own factor, exactly. It is not an interaction, an overhead or a rounding: one descent over an interval returns each present symbol's sub-interval, which IS the walk's rank, and running-summing the counts gives the walk's smaller-count for every symbol at once. Taking both is taking the second one twice.factor against the published loopthe compound walk11xthe enumeration78xthe two, multiplied860xnothing measures thisan index with both78xthe shortfall is 11x, and the walk's factor is 11x6 patterns · 1 error · sigma 21the larger of the two, not the product
Fig. 1 The two factors, the product a reader would compute from them, and what an index with both actually costs.

The shortfall is one of the factors

859.7 divided by 78.2 is 11.0.

That is the compound walk’s own factor, to three significant figures. Not approximately, not to within an interaction term, not a shared constant — the same number.

A shortfall equal to one of the two factors says that one of them contributed nothing. The combined index is the enumeration alone, and adding the walk to it changes no operation count at all.

The absolute numbers behind those factors are worth having because the shortfall is easier to feel in operations than in ratios.

Twelve patterns of eight characters, one error, over eight thousand characters of a twenty-symbol alphabet.

The published loop spends 1,478,400 bit-vector ranks. The compound walk inside that loop spends 134,400. The enumeration spends 18,916.

The product would predict 1,719 — under two thousand ranks for a search that visits several thousand intervals, which on inspection is not a plausible number at all: the search’s live extensions alone are seven and a half thousand, and each of them costs at least one rank.

That implausibility is worth noticing because it is available without any of this essay’s arithmetic. A predicted cost below the number of things the search does is a prediction that has gone wrong somewhere, and a reader who checked it against the search’s own shape would have caught the error before building anything.

Why

The compound walk returns two things from one descent: a symbol’s rank before a position, and the number of characters sorting before that symbol in the interval. Both are needed by a bidirectional extension — the first to move the forward interval, the second to move the reverse one.

The enumeration returns, for every symbol present, its sub-interval — which is [rank_c(lo), rank_c(hi)), which is the walk’s rank.

And running-summing the counts in leaf order gives, for every present symbol, the number of characters sorting before it — which is the walk’s smaller-count.

So one descent produces the walk’s output for every symbol at once. An index that enumerates and then also walks is computing the walk’s two numbers twice.

One saving is flat in the budget and the other is notThe two savings against the error budget, on 8-character patterns over 8,192 characters of a protein alphabet. The compound walk is flat at 11x — it makes each extension cheaper by a factor that is a property of the alphabet, and the budget does not change the alphabet. The enumeration moves with the budget, because what it removes is the extensions that find nothing and the share of those rises from 64.5% at no errors to 74.7% at 2. The two lines never cross here, and that is the finding rather than a limitation: on a branching search the descent wins at every budget, and the crossing is on the other axis entirely — whether the search branches at all.025507510000.50011.502errors permittedfactor against the published loop64.5%55.8%74.7%the walk: 11xthe descentthe label is the shareof dead extensions6 patterns · sigma 2198x to 105x
Fig. 2 The two factors against the error budget: one flat because it is a property of the alphabet, and one moving because it follows the share of dead extensions.

The identity holds across the budget sweep, which is what makes it a mechanism rather than a coincidence at one point.

At zero errors the walk is 11.0 and the enumeration is 98.1; the product is 1,079 and the measurement is 98.1. At one error: 11.0 and 78.2; product 860, measurement 78.2. At two errors: 11.0 and 104.6; product 1,151, measurement 104.6.

Three budgets, three different enumeration factors, and the shortfall is 11.0 every time. The walk’s factor is flat because it is σ/2 divided by the tree’s depth — a property of the alphabet — and the shortfall tracks it exactly because the shortfall is it.

The check

Because the arithmetic is exact rather than approximate, the check can be too.

The measured combined factor must fall short of the product, by a stated margin. That catches a claim of independence.

And the shortfall must equal the walk’s own factor, to within two per cent. That catches an interaction of some other size, which would mean the mechanism is not the one described.

Both hold: shortfall 11.0, walk factor 11.0.

The second half is what makes this a claim rather than an observation. A check requiring only the first would pass on any shortfall — an overhead, a cache effect, a shared constant — and the explanation offered would be unsupported by anything.

Three ways to extend an interval, on the same searchA search for 8-character patterns within 1 error over 8,192 characters of a 21-symbol alphabet, 6 patterns, counted in bit-vector ranks. The published shape asks about every character of the alphabet at every node: 1,478,400. The compound walk makes each of those questions cheaper by returning the rank and the smaller-count from one descent, which is 11x. Enumerating the interval's symbols removes the questions instead, which is 78x — and the 55.8% of extensions that found nothing are what it removed. All three visit the same live intervals in the same order and report the same occurrences; that is the check, and it is what makes this a plate about cost.a loop over the alphabet1,478,400the compound walk, inside the loop134,40011xone descent per node18,91678x6 patterns · 1 error · sigma 2155.8% of the extensions were dead
Fig. 3 The three machines the factors come from: a loop over the alphabet, the compound walk inside that loop, and one descent per node.
Two savings that do not multiply, and the amount by which they do notThe compound walk is 14x and the enumeration is 109x against the same baseline, so a reader with both numbers computes 1,533x for an index that has both. The measured figure is 109x, and the shortfall is 14x — which is the compound walk's own factor, exactly. It is not an interaction, an overhead or a rounding: one descent over an interval returns each present symbol's sub-interval, which IS the walk's rank, and running-summing the counts gives the walk's smaller-count for every symbol at once. Taking both is taking the second one twice.factor against the published loopthe compound walk14xthe enumeration109xthe two, multiplied1,533xnothing measures thisan index with both109xthe shortfall is 14x, and the walk's factor is 14x6 patterns · 1 error · sigma 27the larger of the two, not the product
Fig. 4 The same comparison on a twenty-six letter alphabet, where both factors are larger and the shortfall is still the walk’s own.

Where the two savings do differ

They are not the same operation and it would be wrong to conclude that the walk is redundant.

The walk answers about one named symbol. Its cost is one root-to-leaf descent, 2⌈log₂ σ⌉ ranks.

The enumeration answers about every present symbol. Its cost is 2d(1 + log₂(σ/d)) ranks for d symbols present.

At d = 1 those are the same. At d = 20 the enumeration costs about seven times the walk — and returns twenty times as much.

So on a search that wants one symbol, the walk is cheaper. The saving that is a loss measures that: 10.0 ranks a step against 19.0 on an exact search, a factor of 1.90 the wrong way.

The two savings therefore split by the shape of the search rather than stacking, and both are worth having in one index because both kinds of query occur.

Reading the two descriptions side by side

The redundancy is visible in the two operations’ own accounts once they are put next to each other, and it is worth doing because the point of the essay is that it could have been seen without a measurement.

The count that was already there describes the compound walk: “at each level the walk already computes one of the two child counts, and when the code says go right the other one — every symbol that went left, all of which sort before c — is the difference between the two.” One symbol, one path, two numbers.

Asking about symbols that are not there describes the enumeration: “each reported symbol comes with the interval it occupies in its own leaf — [rank_c(lo), rank_c(hi)) — which is exactly the interval a search would extend into.” Every symbol, one subtree, an interval each.

The second sentence contains the first’s rank. What it does not say is that running-summing the counts gives the first’s smaller-count too — that is one further step, and it is the step nobody had taken because the enumeration’s own strand was about pruning rather than about counting.

So the redundancy is one inference away from two published descriptions, and the inference is what else that output already contains — which is a question about an operation’s results rather than about its cost, and is not the question either strand was asking.

The general form of the mistake

A reader multiplying two factors is applying a model, and the model is that the two savings act on disjoint work.

That model is right often enough to be a default. Two optimisations to different phases of an algorithm, two compressions of different arrays, a faster inner loop and a better pruning — all multiply, because each removes a fraction of what the other left.

It fails when the two act on the same work, and then the combined saving is not the product and not the sum but roughly the larger of the two.

Distinguishing the cases needs one question about each saving: what does it remove? Not how large it is.

The walk removes σ ranks from computing a smaller-count. The enumeration removes the extensions those ranks were being spent on. The second’s removal includes the first’s, so the first has nothing left to do.

That question is answerable from the two descriptions, before any measurement, and it would have got the right answer here. What the measurement added is the exactness.

Three kinds of non-composition

Since the failure mode has a shape, it is worth setting out the three shapes a pair of savings can have when they do not multiply, because only one of them is this one.

Subsumption. One saving’s removal contains the other’s, so the combination is the larger alone. This pair. The signature is a shortfall exactly equal to one factor.

Overlap. Two savings remove intersecting but distinct sets of work, so the combination is between the larger and the product. The signature is a shortfall between one and the smaller factor, and no exact identity.

Interference. One saving makes the other’s work harder — a compression that slows a lookup the other’s pruning depends on. The combination can be worse than either alone. The signature is a combined figure below the larger factor.

The three are distinguishable by the shortfall alone, before anything is understood about the mechanism, which makes the shortfall worth computing whenever two savings are applied together.

This collection has an instance of the second: three savings in three currencies measured a set of filtering improvements whose combination fell between the larger and the product, and the reason was that two of them pruned overlapping candidate sets. And it has no clean instance of the third, which is the one to watch for.

The size ladder, and the last rung spends what the others savedA bidirectional index over 16,384 characters at one sampled position in 32, with each change applied on top of the last. Dropping the reverse half's locating apparatus takes it to 90.4%. Representing the remaining marks as an Elias-Fano array takes it to 84.2% — and that second step is worth 15,570 bits against the first step's 24,080, which is most of a saving that was attributed entirely to the first. The last rung is not a saving at all: it spends the whole of what was saved on sampling the surviving half four times as densely, and lands at 96.4% of where it started with a locate several times faster. That is the trade the strand exists for, and it is one plate rather than two.both halves locate250,584100.0%the reverse half counts only226,50490.4%and its marks are Elias-Fano210,93484.2%the saving spent on sampling241,47596.4%16,384 characters · one in 3284.2% smaller, or the same size and faster
Fig. 5 The pair that does compose: size changes applied cumulatively, where each step’s saving is what the previous step left.

The other two pairs

This strand has three savings and the other two pairs behave differently, which is what makes the classification useful rather than a rule about these two.

The marks and the walk. One is bits, the other operations. A search costs 12,486 ranks whether the marks are plain or sparse — identically, checked as an equality — and the sparser index is smaller. These compose.

The marks and the enumeration. Same argument. Compose.

So of three pairs, two compose and one does not, and the one that does not is the pair whose members are both about extensions.

That is the whole classification and it can be read off the descriptions: two savings compose when they are about different quantities, and one subsumes the other when one’s removal contains the other’s.

On an exact search the enumeration is the more expensive of the twoThe same three machines on a search that knows which character it wants at every step: 16 exact patterns of 12 characters, in bit-vector ranks per step. The loop costs 107.9, the compound walk 10.0 — 11x, and one walk per step is all an exact search needs. The descent costs 19.0, which is 1.90x the walk, because it reports every symbol present in order to hand back one of them. So the two operation savings are a choice rather than a stack, and what chooses is the shape of the search: a branch wants the descent and a known character wants the walk.a loop over the alphabet107.9the compound walk10.0the cheapest hereone descent per step19.016 exact patterns of 12ranks per step · sigma 21
Fig. 6 Where the walk is still the cheaper of the two: an exact search, which knows its character and does not need every present symbol.

What the plate has to show

Drawing this result was a small design problem worth recording, because the natural plate does not show it.

The obvious figure is three bars: the loop, the walk, the enumeration. That is three savings on one structure’s plate, and it shows the two savings and says nothing about their combination — because the combination is the same bar as the enumeration.

A plate showing only measurements therefore cannot show that a product is wrong, since the product is not a measurement. It has to be drawn: a fourth bar, computed rather than measured, labelled as the number nothing measures.

That is a departure from this collection’s usual habit, where every quantity on a plate is a measured one. The justification is that the product is the reader’s own arithmetic and the plate’s whole subject is that the arithmetic is wrong — so the wrong number has to be visible or the plate is answering a question nobody asked.

The convention it establishes: a plate about a combination draws the product a reader would compute, labelled as unmeasured. The alternative is a warning in prose beside a plate that agrees with the warning’s opposite.

Bits paid and steps spent, against the one dial that moves bothThe whole structure's size as a share of the plain bidirectional index, and the LF steps one occurrence costs, on the same frame and against the same parameter. The size falls from 78.0% to 84.7% as the sampling thins, and the walk to a sampled position rises from 1.5 steps to 67.5. Neither line is the answer on its own: a reader choosing a sampling rate is choosing a point on this pair, and a plate showing only the first reports a structure that gets better forever. The two operation savings this strand measures move neither line, which is what "orthogonal" means here and is why they compose with this and not with each other.020406080255075100125one sampled position in every …of the plain index, per centsize: 84.7%67.5 steps16,384 characters · 8-character patternssize falls, the walk lengthens
Fig. 7 The two currencies the three savings live in, on one frame: the structure’s size and a locate’s steps, against the dial that moves both.

The one thing that would make them multiply

There is a version of these two savings that would compose, and describing it makes the subsumption concrete.

Suppose the enumeration returned only the set of present symbols — no intervals, no counts. A search would then know which characters to extend by and would still have to compute each one’s interval, which is a rank walk, which is where the compound walk would apply.

Under that design the two would compose almost exactly: the enumeration removes the dead extensions and the walk makes each surviving one cheaper. The product would be close to right.

The reason it is not the design is that the intervals are free. The descent carries an interval down the tree and splits it at every node, so by the time it reaches a leaf the interval is in hand — throwing it away and recomputing it with a rank walk would be doing the work twice on purpose.

So the subsumption is not an accident of how the operation happens to be implemented. It is a consequence of the operation being implemented well, and an implementation under which the two savings composed would be one that discarded something it had already computed.

That is a general and slightly counterintuitive point: two savings compose when at least one of them is leaving something on the table. A pair that multiplies cleanly may be a pair where the first could have gone further.

Three ways to extend an interval, on the same searchA search for 8-character patterns within 1 error over 8,192 characters of a 5-symbol alphabet, 6 patterns, counted in bit-vector ranks. The published shape asks about every character of the alphabet at every node: 42,570. The compound walk makes each of those questions cheaper by returning the rank and the smaller-count from one descent, which is 3.00x. Enumerating the interval's symbols removes the questions instead, which is 8.81x — and the 38.3% of extensions that found nothing are what it removed. All three visit the same live intervals in the same order and report the same occurrences; that is the check, and it is what makes this a plate about cost.a loop over the alphabet42,570the compound walk, inside the loop14,1903.00xone descent per node4,8328.81x6 patterns · 1 error · sigma 538.3% of the extensions were dead
Fig. 8 The same three machines on a four-symbol alphabet, where every factor is small and the redundancy costs correspondingly little.

What a published pair of results cannot say

The uncomfortable general point is about how these two results arrived.

Each was measured against the published baseline, correctly, in its own strand. Neither was measured against the other, because neither strand had the other’s structure.

So two correct results, each with a correct factor, produce a wrong answer when combined by the obvious arithmetic — and nothing in either result warns against it.

That is not a failure of either measurement. It is a gap that only a third measurement fills, and the third measurement is nobody’s result: it establishes that one of two existing improvements is redundant with another, which is a negative finding about work already done.

The general instruction is short and it is about reading rather than measuring. Two factors quoted against one baseline may not multiply, and the question to ask is what each removes.

Where else this collection has multiplied

The habit of multiplying factors is common enough that it is worth asking where else this collection has done it, and the answer is instructive: twice, both times correctly, and both times because the savings were about different quantities.

Three savings in three currencies is the closest case by name and is not the same thing: its three savings are in different units — candidates, comparisons and bits — so they were never multiplied, only reported side by side.

The saving, spent takes a size saving and converts it into a speed improvement by spending it on a denser sampling. That is not a product either; it is an exchange, and the exchange rate is the measurement.

And a factor of fourteen, for four per cent reports one saving against one cost, which is a ratio rather than a composition.

So this is the first pair in this collection where two factors on the same quantity were available to multiply, and the first where the product would have been wrong. That is a small sample and it suggests the trap is rarer than it feels — most improvements to a structure are to different parts of it, and only two savings aimed at the same operation can subsume each other.

Which is also the warning: two savings aimed at the same operation is exactly the situation where a reader is most tempted to multiply, because both are described in the same terms and against the same baseline.

On an exact search the enumeration is the more expensive of the twoThe same three machines on a search that knows which character it wants at every step: 16 exact patterns of 12 characters, in bit-vector ranks per step. The loop costs 136.2, the compound walk 10.0 — 14x, and one walk per step is all an exact search needs. The descent costs 20.6, which is 2.06x the walk, because it reports every symbol present in order to hand back one of them. So the two operation savings are a choice rather than a stack, and what chooses is the shape of the search: a branch wants the descent and a known character wants the walk.a loop over the alphabet136.2the compound walk10.0the cheapest hereone descent per step20.616 exact patterns of 12ranks per step · sigma 27
Fig. 9 The regime where the subsumed saving is the one to use: an exact search on twenty-six letters, where the enumeration costs about twice the walk.

What the strand does with it

The index built here takes both, and the reason is not that both save.

An index supporting only the enumeration would be slower on exact searches by a factor of 1.90. One supporting only the walk would be slower on branching searches by 78. Supporting both costs nothing — they are two methods on one structure, sharing every array — and lets each query use the one that suits it.

So the answer to “do these compose” is no, and the answer to “should an index have both” is yes, and those are different questions that a product would have conflated.

That is the practical residue of the whole essay: the arithmetic is wrong and the engineering decision is unchanged, which is a comfortable place to land and is not where a reader who had multiplied would have landed. They would have expected a search costing 1,719 ranks and got one costing 18,916, and gone looking for the missing factor of eleven in their implementation.

That last consequence is the practical cost of the wrong arithmetic and it is worth naming, because it is larger than the arithmetic itself. A wrong expectation about a cost does not merely mislead; it sends somebody looking for a bug that is not there. The measured index is correct, its operation count is correct, and the only thing wrong is a number the reader computed.

This collection has an established habit for that shape — an assertion that has never rejected anything proves nothing, so a claim about a cost gets a check — and the check here is on the shortfall rather than on either factor. Both factors were already checked in their own strands and both were right. What needed checking was the thing nobody had written down: that they do not multiply, and by how much they fail to.

A check on a claim nobody made is an odd object and it is the right one here. The claim was going to be made, by any reader with two numbers, and it is the check that makes the essay’s headline something the machinery can defend rather than something the prose asserts.

There is one more thing the shortfall is worth stating in, and it is the form a reader can carry to a structure this collection has not measured. Two savings on one operation compose only when they remove different work. The compound walk removes a second descent per symbol; the enumeration removes the symbols. The second removal contains the first, so the first has nothing left to remove, and the composition is the larger of the two rather than their product. That test — does the later saving still have work of its own to take away — is answerable before any measurement, and it is what the arithmetic here was missing.

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.

Bidirectional indexCheckCompositionCompound walkInterval symbolsRankWavelet tree