feat: arch intrinsic feature discovery pattern#8750
Conversation
c452fcb to
4e917a4
Compare
Merging this PR will improve performance by 13.85%
Performance Changes
Tip Curious why this is faster? Comment Comparing Footnotes
|
e0859ef to
db223d1
Compare
db223d1 to
d67d634
Compare
Polar Signals Profiling ResultsLatest Run
Powered by Polar Signals Cloud |
Benchmarks: FineWeb NVMeVerdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (0.942x ➖, 1↑ 0↓)
datafusion / vortex-compact (0.955x ➖, 0↑ 0↓)
datafusion / parquet (0.947x ➖, 1↑ 0↓)
duckdb / vortex-file-compressed (0.958x ➖, 0↑ 0↓)
duckdb / vortex-compact (0.955x ➖, 0↑ 0↓)
duckdb / parquet (0.933x ➖, 1↑ 0↓)
File Size Changes (1 files changed, +0.0% overall, 1↑ 0↓)
Totals:
|
Benchmarks: PolarSignals ProfilingVortex (geomean): 1.142x ❌ How to read Verdict and Engines
datafusion / vortex-file-compressed (1.142x ❌, 0↑ 7↓)
No file size changes detected. |
Benchmarks: TPC-H SF=1 on NVMEVerdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (1.006x ➖, 0↑ 0↓)
datafusion / vortex-compact (1.008x ➖, 0↑ 0↓)
datafusion / parquet (0.997x ➖, 0↑ 0↓)
datafusion / arrow (0.997x ➖, 0↑ 1↓)
duckdb / vortex-file-compressed (1.007x ➖, 0↑ 0↓)
duckdb / vortex-compact (1.002x ➖, 0↑ 0↓)
duckdb / parquet (1.006x ➖, 0↑ 2↓)
duckdb / duckdb (1.003x ➖, 0↑ 0↓)
File Size Changes (9 files changed, -0.1% overall, 3↑ 6↓)
Totals:
|
Benchmarks: TPC-DS SF=1 on NVMEVerdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (1.009x ➖, 0↑ 0↓)
datafusion / vortex-compact (1.006x ➖, 1↑ 1↓)
datafusion / parquet (1.011x ➖, 0↑ 1↓)
duckdb / vortex-file-compressed (1.008x ➖, 1↑ 2↓)
duckdb / vortex-compact (1.003x ➖, 1↑ 2↓)
duckdb / parquet (0.999x ➖, 0↑ 1↓)
duckdb / duckdb (1.025x ➖, 0↑ 13↓)
File Size Changes (6 files changed, -0.0% overall, 2↑ 4↓)
Totals:
|
Benchmarks: FineWeb S3Verdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (1.042x ➖, 0↑ 1↓)
datafusion / vortex-compact (1.063x ➖, 0↑ 0↓)
datafusion / parquet (1.086x ➖, 0↑ 0↓)
duckdb / vortex-file-compressed (1.058x ➖, 0↑ 0↓)
duckdb / vortex-compact (1.051x ➖, 0↑ 0↓)
duckdb / parquet (1.084x ➖, 0↑ 0↓)
|
Benchmarks: Statistical and Population GeneticsVerdict: No clear signal (low confidence) How to read Verdict and Engines
duckdb / vortex-file-compressed (0.953x ➖, 0↑ 0↓)
duckdb / vortex-compact (0.978x ➖, 0↑ 0↓)
duckdb / parquet (0.897x ✅, 6↑ 0↓)
File Size Changes (1 files changed, -0.0% overall, 0↑ 1↓)
Totals:
|
Benchmarks: Clickbench Sorted on NVMEVerdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (1.056x ➖, 0↑ 3↓)
datafusion / parquet (1.014x ➖, 0↑ 0↓)
duckdb / vortex-file-compressed (0.989x ➖, 1↑ 1↓)
duckdb / parquet (0.975x ➖, 0↑ 0↓)
duckdb / duckdb (0.970x ➖, 0↑ 0↓)
File Size Changes (201 files changed, +0.1% overall, 137↑ 64↓)
Totals:
|
Benchmarks: Random AccessVortex (geomean): 1.022x ➖ How to read Verdict and Engines
unknown / unknown (1.018x ➖, 0↑ 0↓)
|
Benchmarks: TPC-H SF=10 on NVMEVerdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (1.002x ➖, 0↑ 0↓)
datafusion / vortex-compact (1.003x ➖, 0↑ 0↓)
datafusion / parquet (0.984x ➖, 0↑ 0↓)
datafusion / arrow (0.984x ➖, 0↑ 0↓)
duckdb / vortex-file-compressed (0.995x ➖, 0↑ 0↓)
duckdb / vortex-compact (0.990x ➖, 0↑ 0↓)
duckdb / parquet (0.990x ➖, 0↑ 0↓)
duckdb / duckdb (0.982x ➖, 0↑ 0↓)
File Size Changes (26 files changed, -0.0% overall, 13↑ 13↓)
Totals:
|
Benchmarks: Clickbench on NVMEVerdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (0.976x ➖, 2↑ 0↓)
datafusion / parquet (1.003x ➖, 1↑ 1↓)
duckdb / vortex-file-compressed (0.981x ➖, 0↑ 2↓)
duckdb / parquet (0.982x ➖, 0↑ 0↓)
duckdb / duckdb (0.960x ➖, 3↑ 0↓)
File Size Changes (101 files changed, +0.1% overall, 95↑ 6↓)
Totals:
|
Benchmarks: TPC-H SF=1 on S3Verdict: No clear signal (environment too noisy confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (1.256x ➖, 0↑ 5↓)
datafusion / vortex-compact (1.208x ➖, 0↑ 6↓)
datafusion / parquet (1.221x ➖, 0↑ 4↓)
duckdb / vortex-file-compressed (1.088x ➖, 0↑ 0↓)
duckdb / vortex-compact (1.074x ➖, 0↑ 1↓)
duckdb / parquet (1.102x ➖, 0↑ 0↓)
|
Benchmarks: Appian on NVMEVerdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (1.011x ➖, 0↑ 0↓)
datafusion / parquet (1.009x ➖, 0↑ 0↓)
duckdb / vortex-file-compressed (1.005x ➖, 0↑ 0↓)
duckdb / parquet (1.007x ➖, 0↑ 0↓)
duckdb / duckdb (0.994x ➖, 0↑ 0↓)
File Size Changes (4 files changed, -0.0% overall, 2↑ 2↓)
Totals:
|
Benchmarks: TPC-H SF=10 on S3Verdict: No clear signal (environment too noisy confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (1.117x ➖, 0↑ 4↓)
datafusion / vortex-compact (1.071x ➖, 0↑ 2↓)
datafusion / parquet (1.166x ➖, 0↑ 3↓)
duckdb / vortex-file-compressed (1.028x ➖, 0↑ 0↓)
duckdb / vortex-compact (1.015x ➖, 0↑ 0↓)
duckdb / parquet (1.103x ➖, 0↑ 1↓)
|
Benchmarks: CompressionVortex (geomean): 0.998x ➖ How to read Verdict and Engines
unknown / unknown (0.971x ➖, 11↑ 2↓)
|
| use vortex_buffer::CpuKernel; | ||
|
|
||
| /// Signature shared by all 1024-bit transpose kernels. | ||
| type TransposeKernel = unsafe fn(&[u8; 128], &mut [u8; 128]); |
There was a problem hiding this comment.
We need to rebase, this is no longer here
There was a problem hiding this comment.
Rebased onto the latest develop (6b27c45) and force-pushed. The rebase was clean — no upstream commits touched the files in this PR, and bit_transpose/mod.rs is unchanged on develop, so the diff here is unaffected.
Generated by Claude Code
There was a problem hiding this comment.
oh, I forgot that this is in my pr, sorry claude
Hot kernels re-ran cached CPU-feature checks on every call: each is_x86_feature_detected! is an atomic load + bit test per call, and sites like select_in_chunk chained three of them per 64-byte chunk. aarch64 paths were separate cfg blocks at every call site. Add vortex_buffer::CpuKernel<F>, a LazyLock-like slot for function pointers: the selector passed to CpuKernel::new runs once on the first get(), and every later call is a relaxed atomic load, a never-taken predicted branch, and an indirect call. No macros; the two transmute_copy calls between F and the atomic pointer are contained in the type, whose constructor statically asserts F is pointer-sized. Selectors model both dispatch dimensions in plain code: one cfg(target_arch) block per architecture that returns early (runtime feature probes as an if-chain inside), then the portable default as the plain tail - no cfg(not(any(...))) negation blocks anywhere. An arm that returns unconditionally (aarch64 NEON needs no probe) makes the tail unreachable there, silenced by an allow(unreachable_code) scoped to just the tail block. Kernel types are unsafe fn pointers so #[target_feature] kernels coerce directly by name with a single narrow unsafe call per site. Tiny inputs are gated before the dispatch so they keep the old fully inlined code path: count_ones_aligned calls the scalar kernel directly below 32 bytes (where SIMD never engaged anyway), and scan_chunks calls the per-architecture unconditional kernel directly for two chunks or fewer. Without these gates the dispatch indirection dominates sub-SIMD-threshold workloads: CodSpeed flagged true_count[128] (~-30%) and rank_single[(1024, 0.1)]; cachegrind measured the rank regression at +27 instructions per call and parity after the gates (37.70M vs 37.71M baseline instructions for 100k rank calls). Converted sites: fastlanes transpose_bits/untranspose_bits, vortex-buffer scan_chunks/select_in_chunk/select_in_word/count_ones_aligned, vortex-array filter_bitbuffer_by_mask, and vortex-mask intersect_bit_buffers_dispatch/intersect_rank_indices_dispatch. Intentionally left: primitive take (already one-time via LazyLock dyn kernel), intersect_mask_driven_dispatch (generic over an iterator type, so its instantiations cannot share one fn-pointer slot), and the compile-time IS_CONST_LANE_WIDTH constant. The count_ones any-length wrappers carry #[target_feature] so the SIMD kernels inline into them, avoiding an extra call level, and the vortex_bitbuffer and rank bench mains pre-resolve the kernel slots (matching the existing CPUID pre-warm precedent) so no single-shot benchmark measurement includes first-call selection. benches/cpu_dispatch.rs compares dispatch overhead per call against the same #[inline(never)] kernel. Fastest-of-3 per 1024 calls: direct 2.44us, CpuKernel 2.44us (indistinguishable, confirmed by disassembly: load + fused null test + indirect jmp), LazyLock<fn> 3.04us, and three cached feature checks per call 4.54us. All touched crates also cargo check clean on aarch64-unknown-linux-gnu. Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DUFxXZLv4xxEqEYcoGWhfJ
d67d634 to
841d0e4
Compare
Currently we have a few places where we have arch/feature specific kernels, they current use slower feature detection for each call.
This PR add a cache for the kernel to use and uses this pattern in the codebase. I think we would later want to register different kernels in the vortex session instead of this, but that can wait.