Skip to content

Resolve bitpacking functions once for bitwidth - #9721

Open
robert3005 wants to merge 6 commits into
developfrom
claude/bitpacked-fastlanes-function-pointers-ckpnee
Open

Resolve bitpacking functions once for bitwidth#9721
robert3005 wants to merge 6 commits into
developfrom
claude/bitpacked-fastlanes-function-pointers-ckpnee

Conversation

@robert3005

Copy link
Copy Markdown
Contributor

Instead of using unchecked_* variants of bitpacking functions resolve the functions once. Avoids finding the right generic function for given bitwidth on every call

@codspeed-hq

codspeed-hq Bot commented Sep 1, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 12.36%

⚠️ Unknown Walltime execution environment detected

Using the Walltime instrument on standard Hosted Runners will lead to inconsistent data.

For the most accurate results, we recommend using CodSpeed Macro Runners: bare-metal machines fine-tuned for performance measurement consistency.

❌ 3 regressed benchmarks
✅ 2091 untouched benchmarks
⏩ 206 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation take[duplicates/repeated/primitive/nonnull/chunks=16/indices=1000] 198.7 µs 233 µs -14.74%
WallTime words_gather_scalar_avx2[65536] 8.3 µs 9.4 µs -11.64%
Simulation compress_fsst[(500, 64, 8)] 483.1 µs 540.6 µs -10.64%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing claude/bitpacked-fastlanes-function-pointers-ckpnee (0c12a8f) with develop (f80b83c)

Open in CodSpeed

Footnotes

  1. 206 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.

@robert3005 robert3005 added the changelog/performance A performance improvement label Sep 1, 2026
Comment thread encodings/fastlanes/src/bitpacking/array/mod.rs Outdated
Comment on lines +211 to +233
#[inline(always)]
fn as_block<P, const N: usize>(slice: &[P]) -> &[P; N] {
match slice.try_into() {
Ok(block) => block,
Err(_) => block_len_mismatch(N, slice.len()),
}
}

#[inline(always)]
fn as_block_mut<P, const N: usize>(slice: &mut [P]) -> &mut [P; N] {
let len = slice.len();
match slice.try_into() {
Ok(block) => block,
Err(_) => block_len_mismatch(N, len),
}
}

/// Kept out of line so the kernel wrappers stay frameless trampolines: the panic formatting
/// would otherwise reserve stack on every call.
#[cold]
#[inline(never)]
fn block_len_mismatch(expected: usize, actual: usize) -> ! {
vortex_panic!("Expected a FastLanes block of {expected} elements, got {actual}")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm..

No inline(always) please.

Are you sure the try_into() doesn't copy?

BitPacked arrays are type erased, so decoding went through the fastlanes
`unchecked_*` entry points, which re-dispatch on the runtime bit width
with a 65-arm match for every 1024-value block (and for every
`scalar_at`).

Add `BitPackedKernels`, a set of function pointers to the const-width
kernel instantiations (`unpack`, `unpack_single`, `unfor_pack`), lazily
resolved once per array through a `OnceLock` on `BitPackedData` and
shared by every decode path: canonicalization, mapped cast, take, filter,
scalar_at, is_constant, between, and the fused FoR decompress. The fused
compare kernel resolves its `unpack_cmp` instantiation once per call,
since it is generic over the comparison closure.

The resolved kernels take slices and check block lengths, so the unsafe
runtime-width calls are gone from the decoding logic.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R9mScTPzB4Vw4PCdSyc862
Signed-off-by: Claude <noreply@anthropic.com>
`bitpack_primitive` still dispatched on the runtime bit width through
`unchecked_pack` for every 1024-value block. Resolve the const-width pack
kernel once per call via `BitPackedPhysical::resolve_pack` instead, so no
path outside kernel resolution matches on the width.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R9mScTPzB4Vw4PCdSyc862
Signed-off-by: Claude <noreply@anthropic.com>
Inspecting the release assembly showed two misses. `BitPackedData::kernels`
was not inlined, so `scalar_at` paid an out-of-line call before the
indirect kernel call; mark it `#[inline]`. Every kernel wrapper also
carried the `vortex_panic!` formatting for a block-length mismatch inline,
which reserved a stack frame on the hot path; move it into a `#[cold]`
out-of-line function so the wrappers reduce to a length compare and a
tail jump into the fastlanes kernel.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R9mScTPzB4Vw4PCdSyc862
Signed-off-by: Claude <noreply@anthropic.com>
…ters

Resolve the FastLanes kernels when a `BitPackedData` is constructed rather
than on first use. `BitPackedData::try_new` now takes the array's `PType`,
rejects a bit width wider than the type, and stores the kernels directly,
so the `OnceLock` is gone.

Replace the per-type `ResolvedKernels` enum with a type-erased
`BitPackedKernels<()>`: the function pointers are transmuted to a
placeholder element type for storage and transmuted back by `typed::<P>()`,
which checks the recorded physical `PType` first. This removes the enum,
the `kernels_from` trait method, and the per-call variant match; the
accessor is now a ptype compare and a struct copy.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R9mScTPzB4Vw4PCdSyc862
Signed-off-by: Claude <noreply@anthropic.com>
Make the resolved kernel pointers `unsafe fn` with the same length
contract the fastlanes `unchecked_*` entry points had. The wrappers now
reinterpret the slices with a `debug_assert` and a pointer cast, as the
original code did, instead of a checked `try_into` and an out-of-line
panic. In release the block wrappers reduce to a bare tail jump into the
fastlanes kernel. Callers carry the SAFETY reasoning the checks used to
enforce.

`BitPackedData` no longer duplicates `bit_width`: the resolved kernels
already record it, and `bit_width()` reads it from there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R9mScTPzB4Vw4PCdSyc862
Signed-off-by: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R9mScTPzB4Vw4PCdSyc862
Signed-off-by: Claude <noreply@anthropic.com>
@robert3005
robert3005 force-pushed the claude/bitpacked-fastlanes-function-pointers-ckpnee branch from 7f83e7e to 0c12a8f Compare September 2, 2026 17:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog/performance A performance improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants