Latest Results
fix(arrow): return an error for incompatible Arrow export targets
`execute_arrow_naive` dispatches purely on the requested Arrow `DataType`,
but the bool, null, decimal, list, list-view, and fixed-size-list arms then
call `execute::<T>`, which is documented as panicking when the array's dtype
does not match. Exporting an array to an incompatible Arrow type therefore
panicked instead of returning an error.
Guard each arm with `vortex_ensure!` on the source dtype, mirroring the
existing check in `to_arrow_byte_array`. The guards are per-arm rather than
centralised because compatibility is not symmetric: a Bool source exports to
Int32 via a cast kernel, while a Primitive source cannot produce Boolean, so
a single "type classes must match" rule would reject conversions that work.
Signed-off-by: Robert Kruszewski <robert@spiraldb.com>claude/arrow-target-dtype-check Add a branch-free splat form for word-scale runs
The splat tests, per run, whether the run reaches the end of the current
word. That test is data-dependent, and its predictability tracks the run
length: for runs averaging r bits it fires about r/64 of the time. Well below
a word it almost never fires and predicts perfectly. Around half a word it is
a coin flip, and the mispredictions dominate the loop -- which is why the
per-run cost peaked at run length 32 rather than falling monotonically.
Add a form with no such test. It paints the run from the current bit offset
all the way to the top of the word and stores unconditionally:
word = (word & low_mask(bits)) | (splat << bits);
words[idx] = word;
let completed = (bits + n) / 64;
...
idx += completed;
word = if completed > 0 { splat } else { word };
bits = (bits + n) % 64;
Bits above `bits` are don't-care, so painting past the run's end is free: the
next run blends over them. Two conditions make it correct. The output needs
one word of slack, because a zero-length run arriving once the buffer is
exactly full still stores -- and run ends clamped to the logical length
produce exactly that. And `finish` must mask the last partial word, or the
paint survives past the logical length where `true_count` would read it.
`blend_form_matches_naive` covers both, at three lengths chosen to land on
and off the word grid.
It is not strictly better, so both forms stay, picked by average run length,
which is O(1) from the run count. Blend over branching, by fastest sample:
run length 1 2 8 16 32 48 64 128 1024
non-nullable 0.75 0.79 1.02 1.35 1.71 1.39 1.15 0.86 0.74
nullable 0.82 0.88 1.15 1.37 1.71 1.79 1.74 1.89 1.16
Below a quarter of a word the redundant stores -- up to 64 per word of output
-- cost more than the branch. The nullable kernel crosses over earlier: it
does about twice the work per run, so the extra stores are proportionally
cheaper while a misprediction costs the same.
The blend path is `#[inline(never)]`. This is not a micro-preference: with
the two loops inlined into one function, the branching loop lost 20% with its
own text unchanged, measured against an in-process anchor that did not move.
Expressing the choice as a const generic over one loop did the same. Only
keeping the second loop out of line leaves the first one alone.
On the corpus benchmark, against the previous commit: run16 cases 1.16-1.37x,
run64 cases up to 1.50x, run4 cases unchanged.
Signed-off-by: "Claude" <noreply@anthropic.com>claude/runend-decompress-bools-1t2re2 Latest Branches
0%
claude/arrow-target-dtype-check 0%
codex/skip-scan-expression-optimize +10%
claude/dynvarbinbuilder-review-0zqkhq Ā© 2026 CodSpeed Technology