The other axis

A select that starts near its answer

The cheapest partition of an r-index's run-start values held exact copies in two chunks, one a bitmap of 664 values that every select scanned from its start: 1,065 bits read a select. Capping the chunks bought the reads back by giving up the saving. A select sample every 64 values inside each chunk, its bits charged in the same shortest path, was predicted to bring the uncapped optimum under 150 bits a select for under 3.5 bits a value. It reads 111 for 3.45, where the cap that reads as little costs 3.68. And the middle divergences, predicted to be untouched because their chunks were short, lose two fifths of their reads to the same samples.

Cuts that follow the gathering found the cheapest way to cut an r-index’s run-start values into chunks. The values are positions in a collection of 32 copies of 2,048 characters, few overall and crowded where the copies differ, and sparse on average, dense where it gathers had held them as chunks, each chunk a bitmap, its own Elias–Fano or a run, whichever is cheapest. Choosing the cuts as a shortest path through the sorted values, each chunk charged its cheapest form plus its share of the upper level, gave the cheapest partition exactly. On exact copies it is two chunks: 2 values spread over 63,481 positions, and 664 values gathered in a bitmap over 2,055. That costs 3.27 bits a value, 27% under chunks of 32.

It also costs 1,065 bits read for every select. A select asks for the jj-th value; the upper level points it to the chunk, and a bitmap chunk is scanned from its first bit to the target one, which in a bitmap of 2,055 bits is half of it on average. One Elias–Fano, a position split in two, reads 33 bits a select. A cap on the values a chunk may hold bought the reads back at a steady rate: capped at 64 values, 3.68 bits a value for 116 bits read; at 32, 4.08 for 93.

Its closing section noticed that the cap is a blunt instrument. The scan is long because it starts at the chunk’s beginning, and it could start nearer the target instead. A long chunk could carry its own small directory, so that a select jumps into the chunk and scans only the stretch after the entry it lands on. That is a second level of the same structure inside the long chunks only, and its bits can be charged in the shortest path like everything else, so the optimum can choose to pay for it. The prediction was that on exact copies the uncapped optimum with directories reads under 150 bits a select for under 3.5 bits a value, which no capped partition achieves, and that on the middle divergences it changes nothing, because their chunks are already short.

The first part holds, and the second turns out to be the more interesting failure.

A sample every t values

The directory is a select sample. A chunk of kk values over uu positions gets the offset, within the chunk, of every tt-th value’s bit: in its bitmap for a bitmap chunk, in its high bit vector for an Elias–Fano chunk. Each sample takes ⌈log⁡2(u+1)⌉\lceil \log_2(u + 1) \rceil bits, the width a position needs when it is stored outright, and a chunk has ⌊(k−1)/t⌋\lfloor (k - 1)/t \rfloor of them, since its first value is found at the chunk’s start. A select for the jj-th value of a chunk reads the sample before it, one entry, and scans from that sample’s bit to its own. Runs are read without any scan, as before.

The shortest path now charges each chunk its cheapest form, its samples and the upper level’s share, so the optimum chooses its chunks with the samples’ price in view. With no samples, t=∞t = \infty, the path and its partitions are the earlier essay’s exactly, and the reads are counted by the same model: the upper level’s block to find the chunk, then the bits scanned inside it. It is a model of the bits read, not a timing. Samples are measured every 256, 64 and 16 values, beside the cheapest partition capped at 32, 64, 128 and 256 values and uncapped.

Under 150 bits on exact copies

On exact copies, a select sample every 64 values inside the cheapest uncapped partition costs 3.45 bits a value for 111 bits read a select, where capping the chunks at 64 values costs 3.68 for 116 and the uncapped partition without samples reads 1,065; every 16 values, 3.93 bits for 43On exact copies, 666 values among 65,537: bits a value against the mean bits a select reads after its directory lookups, for the cheapest partition capped at 32 to 256 values a chunk and uncapped, and for the cheapest uncapped partition with a select sample every 256, 64 and 16 values inside its chunks, the samples charged in the path. One Elias–Fano: 9.04 bits a value. Capped: ≤32 4.08 and 93, ≤64 3.68 and 116, ≤128 3.47 and 198, ≤256 3.36 and 378, ∞ 3.27 and 1,065. Sampled: none 3.27 and 1,065, 2 chunks, every 256 3.31 and 380, 2 chunks, every 64 3.45 and 111, 2 chunks, every 16 3.93 and 43, 5 chunks.468567891011log₂ bits a select readsbits a valueone Elias–Fano≤32≤64≤128≤256∞t 256t 64t 16capped, no samplesuncapped, samplest: values a sampleexact copiesa model of reads, not a timing
Fig. 1 Exact copies: bits a value against bits a select reads. Capped partitions: 4.08 and 93 at most 32 values, 3.68 and 116 at 64, 3.47 and 198 at 128, 3.36 and 378 at 256, 3.27 and 1,065 uncapped. Uncapped with samples: 3.31 and 380 every 256 values, 3.45 and 111 every 64, 3.93 and 43 every 16. One Elias–Fano: 9.04 bits a value and 33 read.

On exact copies, a select sample every 64 values inside the cheapest uncapped partition costs 3.45 bits a value for 111 bits read a select, against 3.68 bits for 116 read when the chunks are capped at 64 values. The prediction’s under-150-bits for under-3.5 is met, and the comparison it named is decisive: at about the same reads, the samples are 6% smaller than the cap. Every 16 values they cost 3.93 bits a value for 43 bits read, within ten bits of one Elias–Fano’s reads at 43% of its size; no cap on the plate reads fewer than 93.

The samples keep what the cap gave away. With a sample every 64 values the path still chooses two chunks, the same two, and the bitmap of 664 values over 2,055 positions, a bitmap because at one value in three a sparse representation loses, is kept whole, with ten samples inside it at twelve bits each: 120 bits, 0.18 bits a value, which is the whole of the difference between 3.45 and the uncapped 3.27. The cap at 64 instead cuts the set into twelve chunks, each with its own entry, tag and pointer in the upper level, and pays 0.41 bits a value over the uncapped partition, more than twice what the samples pay, to buy about the same reads. A sample is a cheaper way to find a place inside a chunk than a cut is, because it does not have to say what form the next stretch takes or where its payload lives.

Every 16 values the price of the samples starts to shape the partition: the path cuts five chunks rather than two, the largest of 240 values, because a sample every sixteen values in a sparse stretch costs more than the stretch’s Elias–Fano bits would save by joining it. The optimum then pays half a bit a value for its samples, and still reads a twenty-fifth of what the uncapped partition read.

Where the chunks were short

At divergence 0.0005, samples every 64 values cost 5.35 bits a value for 99 bits read a select, against 5.45 for 94 capped at 64 values a chunk, and the uncapped partition's 5.29 for 175On divergence 0.0005, 896 values among 65,537: bits a value against the mean bits a select reads after its directory lookups, for the cheapest partition capped at 32 to 256 values a chunk and uncapped, and for the cheapest uncapped partition with a select sample every 256, 64 and 16 values inside its chunks, the samples charged in the path. One Elias–Fano: 8.56 bits a value. Capped: ≤32 5.73 and 66, ≤64 5.45 and 94, ≤128 5.33 and 134, ≤256 5.29 and 175, ∞ 5.29 and 175. Sampled: none 5.29 and 175, 30 chunks, every 256 5.29 and 175, 30 chunks, every 64 5.35 and 99, 30 chunks, every 16 5.72 and 55, 33 chunks.56789567891011log₂ bits a select readsbits a valueone Elias–Fano≤32≤64≤128≤256 = ∞t 256t 64t 16capped, no samplesuncapped, samplest: values a sampledivergence 0.0005a model of reads, not a timing
Fig. 2 At divergence 0.0005: capped at 32, 64, 128 and uncapped, 5.73 and 66, 5.45 and 94, 5.33 and 134, 5.29 and 175. With samples every 256, 64 and 16 values: 5.29 and 175, 5.35 and 99, 5.72 and 55.

At a divergence of 0.0005, samples every 64 values cut the uncapped optimum’s reads from 175 bits a select to 99 for one twentieth of a bit a value more, 5.35 bits against 5.29. The prediction said the middle divergences would not change, because their chunks are already short, and the cheapest partition’s median chunk there does hold 18 values. But its largest holds 212, a bitmap laid over the place where the copies gather most, and the largest chunks are where the long scans are: a select into a chunk of 212 values reads on average half of it, and the chunk’s three samples cut every select’s scan to the stretch after the sample before it.

The capped partitions sit next to the sampled ones here, not far above them: capped at 64 values, 5.45 bits a value for 94 read. Where most chunks are short, a cap and a sample act on the same few long chunks, and a cut at 64 values in a gathering of 212 costs only a few upper-level entries. The samples’ advantage, a tenth of a bit a value at the same reads, is a third of what it is on exact copies, because there is one long chunk rather than one enormous one.

At divergence 0.01, samples every 64 values cost 6.05 bits a value for 107 bits read a select, against 6.13 for 96 capped at 64 values a chunk, and the uncapped partition's 5.92 for 931On divergence 0.01, 4,144 values among 65,537: bits a value against the mean bits a select reads after its directory lookups, for the cheapest partition capped at 32 to 256 values a chunk and uncapped, and for the cheapest uncapped partition with a select sample every 256, 64 and 16 values inside its chunks, the samples charged in the path. One Elias–Fano: 6.53 bits a value. Capped: ≤32 6.42 and 65, ≤64 6.13 and 96, ≤128 6.00 and 155, ≤256 5.94 and 247, ∞ 5.92 and 931. Sampled: none 5.92 and 931, 15 chunks, every 256 5.94 and 286, 29 chunks, every 64 6.05 and 107, 57 chunks, every 16 6.52 and 55, 91 chunks.5.7566.256.50567891011log₂ bits a select readsbits a valueone Elias–Fano≤32≤64≤128≤256∞t 256t 64t 16capped, no samplesuncapped, samplest: values a sampledivergence 0.01a model of reads, not a timing
Fig. 3 At divergence 0.01: capped at 32, 64, 128, 256 and uncapped, 6.42 and 65, 6.13 and 96, 6.00 and 155, 5.94 and 247, 5.92 and 931. With samples every 256, 64 and 16: 5.94 and 286, 6.05 and 107, 6.52 and 55.

At a divergence of 0.01 the uncapped optimum reads 931 bits a select, nearly as many as on exact copies, and samples every 64 values bring it to 107 for 6.05 bits a value, against 6.13 for 96 when the chunks are capped at 64. Here the long chunks are not bitmaps over a gathering but Elias–Fano chunks over the near-even spread that the copies drift into, and the cheapest partition makes them as long as 1,202 values because a long Elias–Fano chunk pays its upper-level entry once. A select scans its high part from the start. The samples shorten that scan as well, at a cost of nine hundredths of a bit a value.

At this end of the range the cap and the samples are nearly equal. Every 256 values the samples read 286 bits for 5.94, where the cap at 256 reads 247 for the same 5.94: the cap is slightly better, because the path, given samples, re-cuts the long chunks less than the reads would want. Samples help most where one chunk is very long and dense, and least where many chunks are long and sparse.

When the path answers with cuts

The samples are charged in the path, so the optimum can respond to them, and where it does the response is to cut. On exact copies and at 0.0005, a sample every 64 values leaves the partition exactly as it was, two chunks and thirty, and the samples simply sit inside the long chunks. At 0.005 the same samples take the partition from 25 chunks to 48, and at 0.01 from 15 to 57: the longest chunk at 0.01 falls from 1,202 values to 317.

The reason is the width of a sample. A sample is an offset within its chunk, as wide as the logarithm of the positions the chunk spans, and a long Elias–Fano chunk over a sparse stretch spans many thousands of positions, so its samples cost thirteen or fourteen bits each against twelve in the dense bitmap. Past some length, cutting the chunk and paying an upper-level entry costs the path less than sampling it, and the path cuts. That is why the cap and the samples come out nearly level on the most drifted sets: there the cheapest partition with samples is itself partly a capped partition, chosen by price rather than by rule.

Every set, one setting

Samples cut the reads wherever the cheapest partition keeps a chunk longer than a sample: on exact copies from 1,065 bits a select to 111, at 0.0005 from 175 to 99, and at 0.01 from 931 to 107 — the middle divergences included, where the chunks were said to be short alreadyFor each set, the mean bits a select reads under the cheapest uncapped partition, the cheapest partition capped at 64 values a chunk, and the cheapest uncapped partition with a select sample every 64 values, on a logarithmic scale. the scattered marks: the uncapped optimum 1,371, capped at 64 values 103, samples every 64 values 94, the largest uncapped chunk 1197 values; exact copies: the uncapped optimum 1,065, capped at 64 values 116, samples every 64 values 111, the largest uncapped chunk 664 values; divergence 0.0002: the uncapped optimum 270, capped at 64 values 117, samples every 64 values 103, the largest uncapped chunk 224 values; divergence 0.0005: the uncapped optimum 175, capped at 64 values 94, samples every 64 values 99, the largest uncapped chunk 212 values; divergence 0.001: the uncapped optimum 107, capped at 64 values 89, samples every 64 values 89, the largest uncapped chunk 117 values; divergence 0.002: the uncapped optimum 83, capped at 64 values 77, samples every 64 values 77, the largest uncapped chunk 100 values; divergence 0.005: the uncapped optimum 390, capped at 64 values 88, samples every 64 values 102, the largest uncapped chunk 484 values; divergence 0.01: the uncapped optimum 931, capped at 64 values 96, samples every 64 values 107, the largest uncapped chunk 1202 values.the uncapped optimumcapped at 64 valuessamples every 64 values16642561024the marksexact copies0.00020.00050.0010.0020.0050.01bits a select reads (logarithmic)32 copies × 2,048 characters, and the marksa model of reads
Fig. 4 For each set, the bits a select reads under the cheapest uncapped partition, capped at 64 values a chunk, and uncapped with a sample every 64 values. Exact copies: 1,065, 116, 111. Divergence 0.0002: 270, 117, 103. 0.0005: 175, 94, 99. 0.001: 107, 89, 89. 0.002: 83, 77, 77. 0.005: 390, 88, 102. 0.01: 931, 96, 107. The marks: 1,371, 103, 94.

A sample every 64 values brings every set’s reads to between 77 and 111 bits a select, from as many as 1,371 without them; the cap at 64 values brings them to between 77 and 117. On every set the two settings read within fourteen bits of each other, and the samples are the smaller on every one, by 0.002 bits a value at 0.002 up to 0.23 on exact copies; they read more than the cap on three sets, 0.0005, 0.005 and 0.01, by five to fourteen bits. The scattered marks, a set with no gathering at all, show the same thing from the other side: the uncapped partition there makes a few chunks of hundreds of values and reads 1,371 bits a select, and samples every 64 values take that to 94 at 7.23 bits a value, against 103 at 7.41 for the cap. The plate is the closing section’s comparison drawn for every set at once: a cap and a sample are two ways to bound a scan, and which is cheaper depends on how the set’s long chunks are made.

A sample every 64 values costs 0.18 bits a value on exact copies and 0.01 at 0.002, and every 16 values 0.53 and 0.29; at 16 the path answers the price by cutting more chunks — 5 on exact copies where it kept 2For each set, the bits a value the select samples cost in the cheapest uncapped partition, at a sample every 256, 64 and 16 values, and the number of chunks the path then chooses. the scattered marks: every 256 0.05 bits, 2 chunks, every 64 0.20 bits, 4 chunks, every 16 0.72 bits, 12 chunks, none 2 chunks; exact copies: every 256 0.04 bits, 2 chunks, every 64 0.18 bits, 2 chunks, every 16 0.53 bits, 5 chunks, none 2 chunks; divergence 0.0002: every 256 0.00 bits, 8 chunks, every 64 0.11 bits, 8 chunks, every 16 0.51 bits, 8 chunks, none 8 chunks; divergence 0.0005: every 256 0.00 bits, 30 chunks, every 64 0.05 bits, 30 chunks, every 16 0.36 bits, 33 chunks, none 30 chunks; divergence 0.001: every 256 0.00 bits, 42 chunks, every 64 0.03 bits, 44 chunks, every 16 0.32 bits, 48 chunks, none 42 chunks; divergence 0.002: every 256 0.00 bits, 59 chunks, every 64 0.01 bits, 61 chunks, every 16 0.29 bits, 83 chunks, none 59 chunks; divergence 0.005: every 256 0.00 bits, 29 chunks, every 64 0.06 bits, 48 chunks, every 16 0.39 bits, 86 chunks, none 25 chunks; divergence 0.01: every 256 0.01 bits, 29 chunks, every 64 0.09 bits, 57 chunks, every 16 0.49 bits, 91 chunks, none 15 chunks.every 256 valuesevery 64 valuesevery 16 values00.250.5the marks12 chunksexact copies5 chunks0.00028 chunks0.000533 chunks0.00148 chunks0.00283 chunks0.00586 chunks0.0191 chunksbits a value spent on samples; right: chunks at every 16the cheapest uncapped partitionsamples charged in the path
Fig. 5 The bits a value the samples cost, and the chunks the path then chooses. Every 64 values: 0.18 on exact copies, 0.11 at 0.0002, 0.05 at 0.0005, 0.03 at 0.001, 0.01 at 0.002, 0.07 at 0.005, 0.09 at 0.01. Every 16 values: 0.53, 0.51, 0.36, 0.32, 0.29, 0.39, 0.49. On exact copies the path cuts 5 chunks with samples every 16, against 2 without.

The samples cost 0.01 to 0.18 bits a value at one every 64 values, and 0.29 to 0.53 at one every 16. Their cost follows the long chunks: highest on exact copies and at the two ends of drift, lowest at 0.002, where the cheapest partition’s chunks are a median of 20 values and the largest only 100, so few of them reach a sample at all. Where a set has no long chunks, the samples cost almost nothing because they are almost never there, and save almost nothing for the same reason.

Bounding a scan

A select has two ways to be cheap. It can land in a short chunk, which is what a cap does by making every chunk short; or it can land near its answer inside a long chunk, which is what a sample does. A cap pays for its bound with a cut, an upper-level entry, a tag and a pointer, about 45 bits on these sets, and it cuts the set where the cap falls rather than where the set changes density, which is the misplacement the shortest path was invented to avoid. A sample pays eleven bits for a bound and leaves the chunk whole. On exact copies, where one chunk holds all but two values, the difference is 6% of the index at the same reads.

The earlier essays found a sequence of savings on the same values: chunks beat one Elias–Fano by holding dense places as bitmaps, cuts beat fixed chunks by following the gatherings, and samples now beat caps by keeping the cuts where the gatherings put them. Twenty bits apart began with the observation that a sparse set’s values are best described by how far apart they are; each step since has let the description follow the values a little more closely, and the samples are the first step that is paid in reads rather than in bits.

What the prediction got right

The closing section predicted that on exact copies the uncapped optimum with directories would read under 150 bits a select for under 3.5 bits a value, which no capped partition achieves. With a sample every 64 values it reads 111 for 3.45; the nearest capped partition reads 116 for 3.68. It predicted that the middle divergences would not change, since their chunks are short. Their median chunk is short, and their largest is not: at 0.0005 the samples take the reads from 175 bits a select to 99, two fifths of them, because the reads were all in the one long chunk the median does not see.

The question was whether a cap on chunk length is the right way to buy back reads, or whether a cheapest partition should be allowed to make its chunks as long as the set wants and carry its own way into each one. On the sets where the set wants one very long chunk, the second, by 6% of the index. Where it wants many long sparse chunks, neither, by a tenth of a bit either way.

What the samples leave out

Samples by count. A sample falls every tt values, so in a sparse stretch of a chunk it may be hundreds of bits from the next. A sample every ss bits would bound the scan in bits directly; nothing here measures it.

One spacing at a time. The path is given one tt for every chunk. A path free to choose each chunk’s spacing, or none, would sit on or under every curve here, since each tt measured is one of its choices; it was not run.

One collection. As in the earlier essays, 32 copies of 2,048 characters, drifted at seven divergences, and one set of marks, the marks a locating apparatus keeps.

A model of reads. Bits scanned after the directory lookups, counted as the earlier essays counted them, with a sample’s entry charged its width; the count is not the time, and nothing here closes that gap. Nothing was timed.

Still open: a sample every s bits

A sample every tt values bounds how many values a select passes, not how many bits it reads. In a bitmap chunk over a gathering, 64 values span about 200 bits; in an Elias–Fano chunk over a sparse stretch, 64 values span 64 ones and their gaps in the high part, more bits again; and inside a chunk whose density changes, one sample’s stretch can be ten times another’s. A sample every ss bits of the chunk’s bit vector instead bounds the scan at ss whatever the density, and costs a count of the ones before it rather than a position, which is often narrower.

The measurement that follows gives the chunks rank samples every ss bits, ss from 64 to 1,024, charged in the path as before, and draws the frontier against the samples by count. The prediction is that on exact copies, where the long chunk is one dense bitmap, the two kinds of sample draw the same frontier, and that at 0.005 and 0.01, where the long chunks are sparse Elias–Fano chunks, samples by bits read a third less at the same cost, since there 64 values span three or four times as many bits as in the bitmap. It could fail on the select itself. A rank sample tells a select how many ones precede it, so the select must find its target’s sample by a search over samples rather than jump to one, and that search reads entries a value-indexed sample never has to. The question is whether a select is better served by a directory indexed by what it asks, a rank, or by what it reads, a position.

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.

Compressed bit vectorDynamic programmingElias fanoIndex sizeR-indexSelectSparse set