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 that will hold 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 value writes its gap from the previous value as unset bits, then one set bit. The first value is stored asbase, so the set bit for indexisits at(v[i] - base) + i, and the array takeslen + (max - base)bits. For[3, 5, 5, 9],baseis 3 and the bits are1 001 1 00001.This is the upper-bits array of Vigna's quasi-succinct indices (section 4), counted from
baserather than from 0. Unlike rise-rs and vers, there is no sentinel bit before the first value and no guard bit after the last. That way, slicing can trim the bits as they are, and a search by value will need a branch at each end instead.Read per bucket, these are the same bits as the bucket form of EF's upper bitmap, where each bucket's count is written in negated unary, minus the last bucket's closing zero.
The metadata is two
u64s,baseandmax. The dtype must be a non-nullable unsigned integer. With FoR above EF, as the issue proposes, the upper parts are never negative, so the parent can always give this child an unsigned ptype.Logical values. A
Unaryarray holds one value per element, not one count per bucket. As EF'supperchild it then has the parent's length, like the children of FoR and BitPacked, and an unfused EF decode is just(upper << width) | lower.Validation.
try_new,deserializeandwith_bufferscheck the dtype, thatbase <= maxandmaxfits the ptype, the bit length, a zero bit offset, that the first and last bits are set, and that exactlylenbits are set. The last check is one popcount. Any array that passes encodes some non-decreasing sequence frombasetomax, so the readers need no per-value checks, and a point read always agrees with a full decode.Encoding.
unary_encodereturnsNonefor nullable, signed or non-integer input, for unsorted input, and when the bits would take more than the plain array'slen × bit width.Reads.
executewalks the set bits once.scalar_atfinds its set bit withBitBuffer::select, which scans from the first bit.Unaryslicing should land before the EF parent.IsSortedas exact.Wiring. The crate is a workspace member and dependency, and runs in CodSpeed shard 8.