Repository navigation
Conversation
Merging this PR will degrade performance by 10.95%3 warnings, 1 note
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing Footnotes
|
There was a problem hiding this comment.
Ok so I just realized that there are multiple ways that we can imagine the canonical / semantic values of the upper bits.
For example, these two arrays can be thought of as the same:
"delta" encode upper values: [0, 0, 2, 2, 2]
0, 0, 2, 0, 0
-> 1, 1, 001, 1, 1, (0)
bucket 00 01 10
bucket counts: [2, 0, 3]
-> 110 0 1110
11001110
Notice how these are the same thing!
So I think what we need to decide is what we actually want this logical array to represent, should it be the "delta" encoded upper values (which are not exactly delta but close), or the "bucket counts", by which I mean how many upper bit values fall into each bucket.
I was thinking about the buckets, but I can see how both of these are valid representations of the data. However, in my mind, a general Unary encoding should probably be represented as n 1s followed by a closing 0.
I don't think would change basically anything about the Elias-Fano encoding, but it does mean that this implementation of Unary would have to change. For example, we should not require the inputs to be sorted, as a start.
|
Thanks, that makes sense. Let's go with bucket counts: |
## Summary - Tracking Issue: #10605 `BitBuffer::select` finds the `nth` set bit, but nothing finds the `nth` unset bit. The `Unary` encoding in #10609 needs exactly that: each value ends with an unset bit, so a point read selects over unset bits. Elias-Fano's successor and predecessor will need the same. #10609 carries a naive word-by-word scan for now. This PR adds a proper one to `vortex-buffer`, so that `Unary` can use it instead. ## Changes - `BitBuffer::select_unset` returns the position of the `nth` unset bit. This is the select0 counterpart of `select`, and returns `None` past the last unset bit. - **Shared kernels.** Both selects run one body that takes a `const UNSET: bool`; `bit_select` and the new `bit_select_unset` call it with `false` and `true`. With `UNSET`, each word is inverted in a register as it is read; nothing is copied. So the same kernels find unset bits: - the SIMD popcount scan over 64-byte chunks; - the 4× unrolled word scan; - the BMI2 `pdep` + `tzcnt` in-word select. A chunk's unset count is 512 minus its popcount, so the chunk scan does no extra work. `UNSET` is a const generic, so the set-bit path has no runtime branch. - **Partial first and last bytes.** These are the one place the two selects differ. `align_offset_len` zeroes the bits outside the range, and inverting would set them, so they are masked off again. **Tests.** - `test_select_agrees_with_naive` now checks `bit_select_unset` at every offset and length it covers, including `None` past the last unset bit. - `test_select_unset_ignores_bits_outside_range` checks that bits outside the range never count, both within a byte and across a whole chunk. ## API Changes This adds a public method, `BitBuffer::select_unset`. Nothing existing changes. Disclaimer: Claude code has been used to review the code and add appropriate tests --------- Signed-off-by: rapour <reza.apour@gmail.com>
Signed-off-by: rapour <reza.apour@gmail.com>
4e57b0f to
d2cbe40
Compare
Signed-off-by: rapour <reza.apour@gmail.com>
d2cbe40 to
e96311a
Compare
Summary
This is the first step towards Elias-Fano: the
Unaryencoding, which will hold the bucket counts of EF's upper bits. It lives in a newencodings/elias-fanocrate, which will also hold theEliasFanoparent, as discussed on #10377.It is deliberately naive. There is no index, no slice kernel and no compute kernel, so the PR is mostly boilerplate, and the benchmarks set the baselines that later work is measured against. It is in no file edition and
vortex-filedoes not register it, so the default writers reject it.Next up is slicing for
Unary, then the naiveEliasFanoparent withupperandlowerchildren.Changes
Layout. One
BitBuffer. Each valuevis written asvset bits, then one unset bit that closes it. The array takeslen + num_onesbits, wherenum_onesis the sum of the values, and the value at indexkis the number of set bits between thek-th unset bit and the one before it. For[2, 0, 3], the bits are110 0 1110.The metadata is one
u64,num_ones, the number of set bits, which gives the exact bit length together with the array's length. The dtype must be a non-nullable unsigned integer, and the input need not be sorted.As EF's upper child.
Unarywill hold one count per bucket, so its length is the number of buckets, not the parent's length. Its bits are then the upper-bits array of Vigna's quasi-succinct indices (section 4), plus one unset bit that closes the last bucket. EF will read those bits through an extension trait, the way FoR reads BitPacked.Benchmarks. Each shape is the bucket counts that EF's upper child would hold for 16,384 sorted values:
Dense, about one value per bucket;Spread, about half a value per bucket, the sparsest EF gets;Runs, 32 values in every bucket;Gap, with a long run of empty buckets.Wiring. The crate is a workspace member and dependency, and runs in CodSpeed shard 8.