The other axis

A spacing priced by what it saves

A two-level select directory spaces each chunk's select samples so that a select steps through about one rank block, whatever that costs in bits, while the path that cuts the chunks makes the bits least, whatever that costs in reads. Charging each chunk its stored bits plus its expected reads at an exchange rate lets the path choose among a spacing of t₀, 2t₀ and 4t₀ chunk by chunk, and sweeping the rate draws points neither fixed spacing reached: on exact copies 3.824 bits a value for 56.8 bits read, where the best before at 60 bits read was 3.875. The prediction's 3.80 for under 60 is missed by a fortieth of a bit. Across the eight sets the priced path lowers the fewest bits at 60 bits read on five; it changes the scattered marks too, which the prediction said one density would leave alone; and at its highest rate it gives up the directory for many small chunks, a point the plain select samples already beat.

The run starts of an r-index’s Burrows–Wheeler transform are a sorted set of a few hundred to a few thousand values, sparse on average and dense where it gathers. The strand that has been storing them stores them as a partition into chunks (plain bitmaps, Elias–Fano lists, runs) cut by a shortest path over every possible cut, as cuts that follow the gathering first did. A select, the request for the jj-th value, finds its chunk through an upper level and then has to find the value inside it, and the last several essays have been about the second half.

A second level that pays in one band gave each chunk two levels of directory: a select sample every tt values, naming the rank block that holds its value, and a rank sample every ss bits. A select reads its select sample, steps through rank samples until one counts past its target, and scans at most ss bits. A select sample spaced by its chunk then gave each chunk its own tt, the power of two nearest ss over the chunk’s scanned bits a value, so that tt values span about one rank block, and the steps fell to between 0.94 and 1.33 a select on every set. The cost was bits: on exact copies at s=64s = 64 the per-chunk spacing reads 54.8 bits a select for 3.875 bits a value, where a fixed spacing of 64 read 62.0 for 3.763.

That essay’s closing section put the two halves of the design against each other. The spacing rule chooses each chunk’s spacing to make the steps one, whatever that costs in bits; the path chooses the chunks to make the bits least, whatever that costs in reads. Neither sees both. A path could, if each chunk were charged its stored bits plus its expected read bits times an exchange rate λ\lambda — bits stored a bit read — and allowed to choose among spacings of t0t_0, 2t02t_0 and 4t04t_0. The prediction was that on exact copies the priced path would reach under 60 bits read for under 3.80 bits a value, and under 70 for under 3.70; that on the scattered marks it would change nothing, since one density has one right spacing; and it named the risk that selects in a real r-index follow the matches, not the values.

The price the path is charged

Everything is the earlier essays’ but the choice of spacing. The eight sets are the strand’s: the run starts over exact copies, over copies at six divergences from 0.0002 to 0.01, and over scattered marks. The path, the chunk forms, the rank samples and the model of reads are unchanged: a select reads the upper level and the chunk’s entry, the select sample, every rank sample stepped, and the bits it scans in its block.

What changes is what the path is charged for a chunk’s directory. A chunk of kk values whose scanned vector holds dd bits a value may take a select spacing of t0t_0, 2t02t_0 or 4t04t_0, and takes the one that minimises its stored select-sample bits plus λk\lambda k times the bits a select is expected to read in it. The expected read is a model, because the path prices every candidate chunk and cannot replay every one: the select sample’s bits, plus 1+td/2s1 + td/2s rank samples stepped — the mean number of block boundaries between a sample’s value and a target up to tt values after it — plus half a block of scan. A chunk with no rank samples at all is modelled as scanning half its vector. The path is charged the same sum, so with λ\lambda in view it may cut different chunks too.

The rate runs from 0.001 to 0.1 bits stored a bit read, at rank samples every 64 and every 128 bits. Every number below is the replayed read, never the modelled one, and every point is compared with every directory the strand has measured on the same set: two levels at fixed spacings of 64, 128 and 256, two levels spaced per chunk, select samples alone and rank samples alone.

Exact copies: priced at a rate of 0.01 bits stored a bit read, the path reaches 3.824 bits a value for 56.8 bits read, between the per-chunk spacing and a fixed spacing of 64; under 3.80 bits a value it reads 62.0 at best, and at a rate of 0.1 it gives up the directory for many small chunks and 5.033 bits a valueexact copies: bits a value against bits read a select. Earlier directories — two levels at fixed select spacings, two levels spaced per chunk, select samples alone and rank samples alone: 3.763 at 62.0, 3.560 at 84.1, 3.443 at 143.3, 3.734 at 69.5, 3.533 at 87.6, 3.420 at 150.5, 3.721 at 87.6, 3.517 at 97.0, 3.402 at 153.9, 3.875 at 54.8, 3.586 at 80.3, 3.438 at 139.9, 3.450 at 111.1, 3.929 at 42.9, 3.511 at 97.9, 3.718 at 77.1. The priced path, each chunk choosing among t₀, 2t₀ and 4t₀ at rate λ: s 64, λ 0.001: 3.751 at 64.3; s 64, λ 0.003: 3.763 at 62.0; s 64, λ 0.01: 3.824 at 56.8; s 64, λ 0.03: 3.902 at 62.5; s 64, λ 0.1: 5.033 at 42.4; s 128, λ 0.001: 3.533 at 87.6; s 128, λ 0.003: 3.550 at 87.1; s 128, λ 0.01: 3.568 at 85.1; s 128, λ 0.03: 4.273 at 79.9; s 128, λ 0.1: 5.033 at 42.4.3.5044.5055075100125150bits read a selectbits a valuepriced, rank every 64priced, rank every 128best of every earlier directoryexact copiesfaint: earlier directories
Fig. 1 Exact copies: bits a value against bits read a select, every earlier directory as faint dots with their best joined, and the priced path at five rates with rank samples every 64 and every 128 bits.

At rank samples every 64 bits the priced path draws a curve through the gap the earlier essay pointed at. At a rate of 0.001 it takes a spacing four times the per-chunk one on nearly every value and reads 64.3 bits for 3.751 bits a value; at 0.003, 62.0 for 3.763 — the fixed spacing of 64 exactly, reached by a different route; at 0.01, 56.8 bits for 3.824. That last point is new: no earlier directory read under 60 bits for fewer than 3.875.

The prediction’s first number is missed by a fortieth of a bit: under 60 bits read, the priced path’s fewest bits are 3.824, against the predicted 3.80. Its second is missed outright: nothing priced comes under 3.70 bits a value at 70 bits read or fewer, because the rank-every-128 points, which do go under 3.60, read 85 bits and more. And at the top of the rate the curve turns back on itself, which the next sections explain: at 0.03 it reads 62.5 for 3.902, worse on both counts than the per-chunk spacing, and at 0.1 it reads 42.4 for 5.033.

Which spacing each chunk chose

Exact copies, rank samples every 64 bits: at a rate of 0.001 94% of values take a spacing four times the per-chunk one, at 0.01 95% take twice it, and from 0.03 the per-chunk spacing wins — and at 0.1 every chunk is too small to carry a directory at all (100% of values)The share of values, outside run chunks, whose chunk chose each spacing at each rate λ, and the share in chunks with no rank samples, on exact copies with rank samples every 64 bits; with the chunks the path cut. λ 0.001: t₀ 0%, 2t₀ 6%, 4t₀ 94%; no directory 0%; 5 chunks; λ 0.003: t₀ 0%, 2t₀ 6%, 4t₀ 94%; no directory 0%; 5 chunks; λ 0.01: t₀ 5%, 2t₀ 95%, 4t₀ 0%; no directory 5%; 7 chunks; λ 0.03: t₀ 73%, 2t₀ 27%, 4t₀ 0%; no directory 12%; 11 chunks; λ 0.1: t₀ 100%, 2t₀ 0%, 4t₀ 0%; no directory 100%; 51 chunks.λ = 0.0015 chunksλ = 0.0035 chunksλ = 0.017 chunksλ = 0.0311 chunksλ = 0.151 chunkst₀, the per-chunk spacing2t₀4t₀exact copies, s = 64share of values
Fig. 2 Exact copies, rank samples every 64 bits: the share of values whose chunk chose each of the three spacings at each rate, and how many chunks the path cut.

At the lowest rate a read is nearly free, so every chunk takes the sparsest spacing it is offered: 94% of values at 4t04t_0. As the rate rises the choice moves down one step at a time — at 0.01, 95% of values at 2t02t_0; at 0.03, 73% at t0t_0 — and the path’s chunks change with it, from five chunks to seven and then eleven. At 0.1 the path has cut 51 chunks, every one too short to carry a rank sample, and the spacing has nothing left to choose.

So on exact copies the per-chunk spacing of the earlier essay is the choice of a rate between 0.01 and 0.03, and below that rate the steps it bought are not worth their bits: a spacing twice as wide gives up about a quarter of a step a select and saves a twentieth of a bit a value, which at a rate of 0.01 is a good trade.

The rate chooses the chunks

The rate chooses the chunks as well as the spacings: on exact copies the path cuts 5 chunks at a rate of 0.001 and 51 at 0.1, on the scattered marks 12 and 127, at divergence 0.01 55 and 364 — at the top rate no chunk is long enough to carry a rank sampleChunks in the priced path at each rate, rank samples every 64 bits, for exact copies, the scattered marks and divergence 0.01, with the share of values in chunks with no rank samples. exact copies: λ 0.001 5 chunks (0% without a directory), λ 0.003 5 chunks (0% without a directory), λ 0.01 7 chunks (5% without a directory), λ 0.03 11 chunks (12% without a directory), λ 0.1 51 chunks (100% without a directory); the scattered marks: λ 0.001 12 chunks (0% without a directory), λ 0.003 12 chunks (0% without a directory), λ 0.01 12 chunks (0% without a directory), λ 0.03 24 chunks (9% without a directory), λ 0.1 127 chunks (100% without a directory); divergence 0.01: λ 0.001 55 chunks (9% without a directory), λ 0.003 77 chunks (14% without a directory), λ 0.01 104 chunks (23% without a directory), λ 0.03 205 chunks (79% without a directory), λ 0.1 364 chunks (100% without a directory).rate λ, bits stored a bit read (log scale)chunks the path cut (log scale)1101000.0010.0030.010.030.1divergence 0.01the scattered marksexact copiesrank samples every 64chunks, log scale
Fig. 3 Chunks the priced path cut at each rate, rank samples every 64 bits, on exact copies, the scattered marks and divergence 0.01.

The plate shows the second thing the price does, which the proposal did not describe. On exact copies the path cuts 5 chunks at a rate of 0.001 and 51 at 0.1; on the scattered marks 12 and 127; at divergence 0.01, 55 and 364. A high rate makes a long chunk expensive, because even with a directory a select in it reads a select sample, a step or two and half a block, and a short chunk with no directory reads half of a short vector. At the highest rate every chunk on every set is short enough to scan.

That end of the curve is real and not useful. On exact copies the priced path at 0.1 reads 42.4 bits for 5.033 bits a value; select samples alone, measured two essays back, read 43 bits for 3.93. The priced path’s options are two-level directories at one rank width and chunks cut to suit them; it cannot choose select samples alone, and at a high rate it reaches for few reads the only way its options allow, by cutting many small chunks, which costs far more bits than the directory it is avoiding. A rate is only as good as the choices it prices.

The scattered marks

The scattered marks: the per-chunk spacing reads 57.8 bits for 7.436 bits a value; priced at 0.01 the path takes a spacing twice as wide on 78% of the values and reads 60.4 bits for 7.398 — one density has one spacing that makes steps one, not one spacing worth its bitsthe scattered marks: bits a value against bits read a select. Earlier directories — two levels at fixed select spacings, two levels spaced per chunk, select samples alone and rank samples alone: 7.391 at 60.9, 7.233 at 85.0, 7.140 at 145.4, 7.368 at 67.8, 7.206 at 88.8, 7.119 at 144.1, 7.355 at 79.1, 7.193 at 95.7, 7.109 at 146.8, 7.436 at 57.8, 7.233 at 85.0, 7.119 at 142.8, 7.233 at 93.7, 7.857 at 46.1, 7.102 at 157.0, 7.184 at 102.8, 7.347 at 79.6. The priced path, each chunk choosing among t₀, 2t₀ and 4t₀ at rate λ: s 64, λ 0.001: 7.370 at 65.8; s 64, λ 0.003: 7.389 at 61.0; s 64, λ 0.01: 7.398 at 60.4; s 64, λ 0.03: 7.506 at 66.2; s 64, λ 0.1: 8.616 at 50.1; s 128, λ 0.001: 7.195 at 95.3; s 128, λ 0.003: 7.206 at 88.8; s 128, λ 0.01: 7.268 at 85.6; s 128, λ 0.03: 7.893 at 65.2; s 128, λ 0.1: 8.616 at 50.1.77.5088.505075100125150bits read a selectbits a valuepriced, rank every 64priced, rank every 128best of every earlier directorythe scattered marksfaint: earlier directories
Fig. 4 The scattered marks: every earlier directory and the priced path, as on exact copies.

The prediction said the marks would not move, since every chunk there is an Elias–Fano list of about two scanned bits a value and the earlier essay found every one choosing the same spacing. They move. At a rate of 0.01 the path takes twice the per-chunk spacing on 78% of the values and reads 60.4 bits for 7.398 bits a value, against the per-chunk spacing’s 57.8 for 7.436; at 0.003, 61.0 for 7.389.

The prediction confused two senses of right. One density has one spacing that makes the steps one, and the earlier essay found it. It does not have one spacing that is worth its bits, because how much a step is worth depends on the rate, and at most rates a spacing twice as wide is a better trade than the one that makes steps one. On the marks the priced points sit just off the earlier frontier rather than under it — the fixed spacing of 64 already read 60.9 for 7.391 — so the rate there rediscovers a point and does not improve on it.

The model the path paid

The path priced its chunks with a model of a select's reads, and the replay reads 9% less than the model on average over every set and rate — at most 8.7 bits less (divergence 0.01, λ 0.001) — so every priced choice was made against a slightly pessimistic priceFor every set at rank samples every 64 bits and every rate: the modelled bits a select reads inside its chunk (select sample, rank samples stepped at 1 + t·d/2s, half a block or half the chunk's scanned vector) against the replayed bits (select sample, rank samples and scan in the block). exact copies λ 0.001: 58.8 modelled, 53.8 replayed; exact copies λ 0.003: 54.6 modelled, 51.5 replayed; exact copies λ 0.01: 47.7 modelled, 45.3 replayed; exact copies λ 0.03: 42.9 modelled, 40.2 replayed; exact copies λ 0.1: 17.5 modelled, 16.4 replayed; the scattered marks λ 0.001: 58.6 modelled, 51.1 replayed; the scattered marks λ 0.003: 51.3 modelled, 46.3 replayed; the scattered marks λ 0.01: 49.8 modelled, 45.7 replayed; the scattered marks λ 0.03: 43.1 modelled, 38.9 replayed; the scattered marks λ 0.1: 18.8 modelled, 18.1 replayed; divergence 0.0002 λ 0.001: 53.3 modelled, 48.5 replayed; divergence 0.0002 λ 0.003: 53.3 modelled, 48.5 replayed; divergence 0.0002 λ 0.01: 45.2 modelled, 41.6 replayed; divergence 0.0002 λ 0.03: 38.7 modelled, 35.6 replayed; divergence 0.0002 λ 0.1: 17.1 modelled, 16.7 replayed; divergence 0.0005 λ 0.001: 45.3 modelled, 38.9 replayed; divergence 0.0005 λ 0.003: 44.6 modelled, 38.7 replayed; divergence 0.0005 λ 0.01: 38.6 modelled, 35.0 replayed; divergence 0.0005 λ 0.03: 33.9 modelled, 31.9 replayed; divergence 0.0005 λ 0.1: 15.7 modelled, 15.6 replayed; divergence 0.001 λ 0.001: 38.3 modelled, 33.0 replayed; divergence 0.001 λ 0.003: 37.6 modelled, 32.8 replayed; divergence 0.001 λ 0.01: 34.7 modelled, 30.9 replayed; divergence 0.001 λ 0.03: 30.3 modelled, 28.1 replayed; divergence 0.001 λ 0.1: 14.9 modelled, 14.9 replayed; divergence 0.002 λ 0.001: 34.1 modelled, 28.5 replayed; divergence 0.002 λ 0.003: 33.6 modelled, 28.3 replayed; divergence 0.002 λ 0.01: 30.8 modelled, 27.1 replayed; divergence 0.002 λ 0.03: 24.5 modelled, 23.4 replayed; divergence 0.002 λ 0.1: 15.0 modelled, 15.0 replayed; divergence 0.005 λ 0.001: 45.4 modelled, 37.2 replayed; divergence 0.005 λ 0.003: 43.2 modelled, 35.7 replayed; divergence 0.005 λ 0.01: 36.8 modelled, 31.8 replayed; divergence 0.005 λ 0.03: 25.4 modelled, 24.5 replayed; divergence 0.005 λ 0.1: 15.0 modelled, 14.9 replayed; divergence 0.01 λ 0.001: 55.0 modelled, 46.3 replayed; divergence 0.01 λ 0.003: 47.1 modelled, 40.4 replayed; divergence 0.01 λ 0.01: 39.8 modelled, 35.3 replayed; divergence 0.01 λ 0.03: 27.3 modelled, 26.0 replayed; divergence 0.01 λ 0.1: 15.4 modelled, 14.9 replayed.02040600204060bits a select the path expected to read in the chunkbits a select replayed in the chunkreplay = modelexact copiesscattered marksdivergencesrank samples every 64bits in the chunk
Fig. 5 For every set and rate at rank samples every 64 bits: the bits a select was modelled to read inside its chunk, against the bits the replay read.

Every choice above was made against a model of reads, and the plate checks it. Across every set and rate the replay reads about a tenth less inside the chunk than the model expected, and never more; the largest gap is about nine bits, on divergence 0.01 at the lowest rate. The model’s average of the steps a select takes runs high, and it runs high uniformly enough that the choices it drives are the choices a replayed price would drive, give or take the boundary cases.

The boundary cases are where the curve turned back. At a rate of 0.03 on exact copies the path cut eleven chunks and put 12% of values in chunks without a directory, on the model’s account of what those chunks would read; the replay reads 62.5 bits, more than the per-chunk spacing’s 54.8 with more bits stored, so the point is dominated. A price paid on a model is a price paid on the model’s errors, and those land somewhere; here they land at the rate where the path begins to trade directories for chunks.

Every set at sixty bits

At 60 bits read a select or fewer, the priced path lowers the fewest bits a value on 5 of the eight sets — by at most 0.120 (divergence 0.001) — against the best of every directory measured before itFor each set, the fewest bits a value among directories reading at most 60 bits a select: every earlier directory (two levels at fixed and per-chunk spacings, select samples alone, rank samples alone), and the same with the priced path's points added. the scattered marks: 7.436 → 7.436; exact copies: 3.875 → 3.824; divergence 0.0002: 4.608 → 4.608; divergence 0.0005: 5.721 → 5.640; divergence 0.001: 6.360 → 6.240; divergence 0.002: 6.748 → 6.748; divergence 0.005: 7.158 → 7.079; divergence 0.01: 6.520 → 6.489.the scattered marks7.436, unchangedexact copies3.875 → 3.824divergence 0.00024.608, unchangeddivergence 0.00055.721 → 5.640divergence 0.0016.360 → 6.240divergence 0.0026.748, unchangeddivergence 0.0057.158 → 7.079divergence 0.016.520 → 6.489budget 60 bits readbits a value
Fig. 6 On each of the eight sets, the fewest bits a value among directories reading at most 60 bits a select: the best of every earlier directory, and the same with the priced path’s points added.

The headline comparison is the one a second level that pays in one band used: at a fixed budget of reads, how few bits. At 60 bits read the priced path lowers the fewest bits on five of the eight sets: exact copies from 3.875 to 3.824, divergence 0.0005 from 5.721 to 5.640, divergence 0.001 from 6.360 to 6.240, divergence 0.005 from 7.158 to 7.079, divergence 0.01 from 6.520 to 6.489. On the marks, divergence 0.0002 and divergence 0.002 it changes nothing. At 70 bits read it helps on two sets, by a hundredth on divergence 0.0005 and by 0.28 bits a value on divergence 0.01, where 6.520 becomes 6.238.

That is the second level’s band widened again, in the direction the earlier essay found it widening: downward, toward fewer reads. On the five divergent sets, where earlier directories could not read under 50 bits at all, the priced path’s high-rate points are the only ones that do — at seven bits a value or so, which is the price of scanning many short chunks.

The strand’s directories, priced

Read through the rate, every directory this strand has built is a point some price would choose. A select that starts near its answer put select samples alone in each chunk, which is the right directory when reads are dear and bits are cheap: at 43 bits read on exact copies it is still the best thing measured. A sample that has to be found put rank samples alone, which bounds the scan but must be searched for, and still holds its own stretch of the frontier on exact copies, around 77 bits read. The two-level directory with a fixed spacing is the rate of a few thousandths, and the per-chunk spacing the rate of about a fiftieth.

That reading has a practical side. A structure that stores the run starts does not have to choose its directory once and for all; it can be built at a rate, and the rate is a statement about the machine it runs on and the workload it serves — how dear a bit read is against a bit stored, which is a question about caches and queries rather than about the set. The essays before this one each chose a directory and measured it; the price is the instrument that says which one a given machine should have chosen.

It is also an instrument with a limit the plates make plain. The rate can only choose among the directories it is offered, and at the top of its range it chose many small chunks because the plain select samples were not on its menu. A complete price would offer every directory the strand has measured to every chunk and let the rate choose among all of them.

What the rate is a price of

The question the earlier essay left was whether a directory can be fitted to a rate of exchange between storage and reading, or whether the rate itself depends on which values are asked for. The plates answer the first half: it can, and the fitting is worth a few hundredths of a bit a value at a fixed budget of reads on most sets, a quarter of a bit on one, and nothing on three.

They do not answer the second half, because every select here is uniform over the values. The risk the proposal named is untouched: in an r-index the selects follow the matches of the patterns being located, and a chunk holding the run starts of a common pattern is asked far more often than its share of values. A rate charged per value, as here, is a rate for a workload that asks every value equally. Every occurrence at the same price and the sampling that follows the runs built the parts of the r-index that issue these selects, and neither said anything about which values they ask for.

What the plates add is a correction to how the earlier essays read their own spacing rule. A select sample spaced so that its values span one block is not the right spacing; it is the right spacing at a rate of about a fiftieth of a bit stored for every bit read. Below that rate wider is better, above it narrower — until the price begins to choose the chunks instead, which is where the path’s own options run out.

What the replays do not cover

Uniform selects. Every select is equally likely to ask for any value, in the model and the replay alike.

Three spacings a chunk. The path chooses among t0t_0, 2t02t_0 and 4t04t_0, as proposed. Half the per-chunk spacing was not offered, and at the higher rates the choices pile up at t0t_0, so it might have been taken.

One rank width at a time. Each run fixes ss at 64 or 128 for every chunk; a path that chose the rank width per chunk as well was not run.

The read model. The path is charged a model; every reported read is replayed. The model’s errors are measured above, and they are a tenth of the reads, never in the cheap direction.

Still open: a rate for the values that are asked

Every price here assumes that each value of the set is selected equally often, and the proposal’s own risk said that is not how an r-index asks. When a pattern is located, the run-start values it selects are those of its own matches’ runs, and a frequent pattern asks for the same values again and again.

The measurement that follows draws selects from a workload of patterns over the collection’s text — the run starts each pattern’s locating actually selects — weights each value by how often it is asked, and charges each chunk its stored bits plus the rate times its reads weighted by that workload, beside the uniform price here. The prediction is that on exact copies the weighted price puts denser select samples in the few chunks holding the common patterns’ run starts and sparser ones elsewhere, reaching about 3.80 bits a value for 55 bits read on that workload, and that a directory priced uniformly reads about a tenth more on it than it does on uniform selects. It could fail if the asked values are spread across every chunk, so that weighting changes no chunk’s choice, which on exact copies, where every copy’s run starts are interleaved, is likely. The question is whether a directory can be fitted to its workload as well as to its set, or whether on a repetitive collection every chunk is asked about equally after all.

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.

Compressed bit vectorElias fanoIndex sizeR-indexSelectSparse setTime space tradeoff