Eliminate movprfx stalls in SVE BBQ D1Q4 bulk scorer (~14% throughput improvement)#155001
Eliminate movprfx stalls in SVE BBQ D1Q4 bulk scorer (~14% throughput improvement)#155001ChrisHegarty wants to merge 7 commits into
Conversation
|
Pinging @elastic/es-search-relevance (Team:Search Relevance) |
|
Pinging @elastic/es-perf (Team:Performance) |
|
Hi @ChrisHegarty, I've created a changelog YAML for you. |
🔍 Preview links for changed docs⏳ Building and deploying preview... View progress This comment will be updated with preview links when the build is complete. |
ℹ️ Important: Docs version tagging👋 Thanks for updating the docs! Just a friendly reminder that our docs are now cumulative. This means all 9.x versions are documented on the same page and published off of the main branch, instead of creating separate pages for each minor version. We use applies_to tags to mark version-specific features and changes. Expand for a quick overviewWhen to use applies_to tags:✅ At the page level to indicate which products/deployments the content applies to (mandatory) What NOT to do:❌ Don't remove or replace information that applies to an older version 🤔 Need help?
|
This was factored out of a trial run of a harness I've been working on to find performance optimisations for Elasticsearch. It was very early stages when it found this change, but the change is good and I wanted to extract as a standalone PR rather than hold up as I continue to iterate on the harness itself in collaboration with @tveasey and team.
This changes 16 SVE AND intrinsics in the
dotd1q4_inner_bulkhot loop fromsvand_u8_z(zeroing form) tosvand_u8_x(relaxed form), eliminating unnecessarymovprfxinstructions that clang-21 emits. On Graviton 4, this yields a 12–16% throughput improvement for bulk BBQ binary quantized vector scoring.While profiling BBQ D1Q4 scoring on a Graviton 4 (SVE 128-bit) instance, we noticed the inner loop had an unexpectedly high instruction count. Disassembling the clang-21 output revealed a
movprfxinstruction before every and — 16 per loop iteration, 32 extra instructions total. Themovprfxis required by the SVE architecture to set up the destination register for the zeroing predication form (_z), since SVE's destructive two-operand AND can't both zero inactive lanes and operate in-place without it.Why the change is correct
The loop loads vectors using
svld1_u8(pg, ...)wherepg = svwhilelt_b8(r, length). The masked load already zeros inactive lanes in the loaded data. Since inactive lanes of both operands(v0..v3 and q0..q3)are already zero, the result of ANDing them is zero regardless of whether the AND itself zeros inactive lanes. We can therefore safely usesvand_u8_x(svptrue_b8(), ...)- the all-true predicate means the AND computes all lanes unconditionally, and the compiler can emit a single unpredicated AND instruction with nomovprfx.Performance results (Graviton 4, clang-21, JMH)
Scalar, NEON-tier, and D2Q4 paths are unaffected (the change only touches the SVE tier-2 code in vec_bbq_2.cpp).