Skip to content

Optimize to_bitmask() on NEON - #344

Merged
Shnatsel merged 1 commit into
linebender:mainfrom
Shnatsel:faster-to-bitmask
Aug 24, 2026
Merged

Optimize to_bitmask() on NEON#344
Shnatsel merged 1 commit into
linebender:mainfrom
Shnatsel:faster-to-bitmask

Conversation

@Shnatsel

@Shnatsel Shnatsel commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

A stab at #342 to see what it would take to optimize it.

Summary of changes:

  • Add dedicated handwritten kernels for 8x32 (256-bit) and 8x64 (512-bit) masks
  • Narrow 16/32/64-bit masks into 8-bit ones (or wider, if 128-bit width is reached before 8-bit elements)

The narrowing reduction into base 8-bit kernels helps a lot; mask16x16 sees 1.5x to 2.x throughput improvement depending on the CPU.

The 256-bit kernel was discovered by having Codex generate a dozen candidates and select the best one according to llvm-mca. It saves 4-5 cycles of latency and provides 30% better throughput compared to the generic split-combine lowering into 128-bit ops. It is tested exhaustively on all possible valid mask patterns.

I'm not entirely convinced this is worth optimizing, so marking it as draft. If this really is an important operation for some algorithms, similar optimizations should also be extended to AVX2 and possibly SSE4.2; AVX-512 is already zero-cost.

Comment thread fearless_simd_gen/src/mk_neon.rs
@Shnatsel

Copy link
Copy Markdown
Contributor Author

x86 has dedicated movemask intrinsics for this that lower into a single instruction even in SSE2, and zip/unzip on AVX2 is expensive. LLVM is also smart enough to rewrite the recursive decomposition into packing trees. So none of the tricks from here really apply to x86.

@Shnatsel

Copy link
Copy Markdown
Contributor Author

The complexity is isolated to NEON so it seems worthwhile to add this. Marking as ready for review.

@Shnatsel
Shnatsel marked this pull request as ready for review August 24, 2026 11:31
lo | (hi << 8)
let rotated = vextq_u8::<8>(bits, bits);
let paired = vzip1q_u8(bits, rotated);
vaddvq_u16(vreinterpretq_u16_u8(paired)) as u64

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Will this work correctly on big-endian targets? Not sure if we care about those? Maybe we should run those in CI as well, as far as I can tell we currently don't.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Probably doesn't matter, just wondering!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Big-endian Aarch64? No, we definitely don't care. Support for it was removed from Linux 7.4 because it is never used.

@LaurenzV LaurenzV left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Otherwise fine from my side, without having tried to understand the new approach!

@Shnatsel

Copy link
Copy Markdown
Contributor Author

The old approach was splitting 256/512-bit vectors into 2/4 128-bit vectors, reducing their values to bits individually and combining the result.

The new approach instead narrows elements first (u32->u16, u16->u8, etc) to reduce the width of the vector in bits, until it reaches either 128 bits or u8 elements, and then goes through the handwritten u8 codepaths.

Does that make sense?

@Shnatsel
Shnatsel added this pull request to the merge queue Aug 24, 2026
Merged via the queue into linebender:main with commit fe7493c Aug 24, 2026
22 checks passed
@Shnatsel
Shnatsel deleted the faster-to-bitmask branch August 24, 2026 13:00
@LaurenzV

Copy link
Copy Markdown
Collaborator

Oh yeah it does, I meant more like I didn't spend time trying to verify all the used intrinsics, haha. 😄

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants