Repository navigation
Conversation
Signed-off-by: rapour <reza.apour@gmail.com>
Merging this PR will degrade performance by 29.37%
|
| Mode | Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|---|
| ❌ | WallTime | threshold_neon[8] |
1.4 µs | 2.2 µs | -36.14% |
| ❌ | WallTime | filter_neon[64] |
1.8 µs | 2.6 µs | -31.56% |
| ❌ | WallTime | threshold_neon[64] |
2.6 µs | 3.2 µs | -19.37% |
| 🆕 | Simulation | decode[Dense] |
N/A | 1.2 ms | N/A |
| 🆕 | Simulation | decode[Gap] |
N/A | 1.3 ms | N/A |
| 🆕 | Simulation | decode[Runs] |
N/A | 1.2 ms | N/A |
| 🆕 | Simulation | decode[Spread] |
N/A | 1.3 ms | N/A |
| 🆕 | Simulation | encode[Dense] |
N/A | 1.3 ms | N/A |
| 🆕 | Simulation | encode[Gap] |
N/A | 1.4 ms | N/A |
| 🆕 | Simulation | encode[Runs] |
N/A | 1.2 ms | N/A |
| 🆕 | Simulation | encode[Spread] |
N/A | 1.3 ms | N/A |
| 🆕 | Simulation | scalar_at_random[Dense] |
N/A | 445.1 µs | N/A |
| 🆕 | Simulation | scalar_at_random[Gap] |
N/A | 1.8 ms | N/A |
| 🆕 | Simulation | scalar_at_random[Runs] |
N/A | 286.5 µs | N/A |
| 🆕 | Simulation | scalar_at_random[Spread] |
N/A | 606.4 µs | N/A |
| 🆕 | Simulation | scalar_at_sequential[Dense] |
N/A | 436.2 µs | N/A |
| 🆕 | Simulation | scalar_at_sequential[Gap] |
N/A | 3.1 ms | N/A |
| 🆕 | Simulation | scalar_at_sequential[Runs] |
N/A | 279.2 µs | N/A |
| 🆕 | Simulation | scalar_at_sequential[Spread] |
N/A | 592.5 µs | N/A |
| 🆕 | Simulation | slice_decode[Dense] |
N/A | 1.3 ms | N/A |
| ... | ... | ... | ... | ... | ... |
ℹ️ Only the first 20 benchmarks are displayed. Go to the app to view all benchmarks.
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing rapour:feat/elias-fano-unary-delta (4e57b0f) with develop (ee6da96)
Footnotes
-
12 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩
-
43 benchmarks were run, but are now archived. If they were deleted in another branch, consider rebasing to remove them from the report. Instead if they were added back, click here to restore them. ↩
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
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.