Skip to content

BitPacked compare and between are 3x to 7x slower on baseline x86-64 than CodSpeed shows #10396

Open

Description

@mightsleep

Problem or motivation

Following up on the analysis in #8280: Vortex builds for baseline x86-64 (SSE2) by default (the vx release binary, crates.io users without target flags), while CodSpeed builds with +avx2 and the walltime legs use avx2, avx512 and neon. SSE2 has no 64-bit compare, so the BitPacked kernels compare u64 values one at a time there. stream_predicate stays on the inline baseline path on purpose (wider kernels behind #[target_feature] would not inline the predicate), so this gap does not show up in any benchmark.

Measured on develop (2be9821), Zen 5, 131072 u64 values, BitPacked 27 bits, divan median:

baseline x86-64 target-cpu=native
BitPacked Lt against a constant 72.6 µs 13.8 µs
BitPacked between 112 µs 42.1 µs
FoR Eq above the range 45.2 µs 14.5 µs

Proposed solution

BitPacked knows the bit width W, so for W <= 31 every value and the constant fit in i32, and SSE2 does have a 32-bit compare: put the low halves of four u64 values side by side (shuffle_ps), one pcmpgtd, movmskps for four mask bits. These are SSE2 intrinsics, part of the x86-64 baseline, so there is no #[target_feature] boundary and the predicate still inlines.

Microbenchmark over one 1024-value block in L1 (the stream_predicate scratch), u64 values with 27 significant bits, v < c into a bitmap:

baseline x86-64 +avx2
today's shape 0.42 ns/value 0.064 ns/value
i32 lanes (W <= 31) 0.058 ns/value 0.060 ns/value

So 7x on baseline and nothing lost with AVX2. A second, smaller one: between as a single unsigned compare, (v - lo) <= (hi - lo) with wrapping subtraction, is 1.56x on baseline for u64, and applies to primitive between too.

Is baseline x86-64 a target you want to optimize for? If so, I can open a PR for stream_predicate, and a baseline leg in CodSpeed would keep it measured.

Additional context

The 0.058 ns/value is bound by loading the u64 scratch from L1. Going further would mean unpacking straight into u32 or u16 lanes, or comparing inside fastlanes' unpack_cmp, which belongs in the fastlanes crate.

Activity

  1. added
    bugA bug issue
    and removed
    featureA feature request
    on Oct 8, 2026
  2. mightsleep commented on Oct 12, 2026

    @mightsleep
    ContributorAuthor

    Correction: compare doesn't use stream_predicate but the fused unpack_cmp (#8239), so the Lt fix belongs in fastlanes (see #9699). I have two changes ready. A bit-width shortcut takes FoR Eq above the range from 32 µs to 1.1 µs. Between through the fused kernel is 1.7x faster on the default build, but still 4x behind native.

    @joseph-isaacs the shortcut is from your #8013. OK if I open a PR building on it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugA bug issue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions