perf(ksw2): arm64 128-bit extd2/extz2 kernel improvements (+10-11% on HiFi/ONT) - #29
Closed
nh13 wants to merge 1 commit into
Closed
perf(ksw2): arm64 128-bit extd2/extz2 kernel improvements (+10-11% on HiFi/ONT)#29nh13 wants to merge 1 commit into
nh13 wants to merge 1 commit into
Conversation
Four changes to the shared anti-diagonal loop, all output-neutral. The per-diagonal score prepass compared against three constants to pick the match, mismatch and N scores. It now indexes a 16-byte table with sf ^ qrr and one byte shuffle, 5 ops down to 2. Query N is remapped to 8 so every index stays <= 12, where NEON's vqtbl1q_u8 and SSSE3's _mm_shuffle_epi8 agree. Each DP rail was shifted one lane with srli+slli+or. Carrying the whole previous vector instead of its last byte makes that a single vext (alignr on x86), three rails in the dual-gap kernel and two in the single-piece one. The exact-max path widened four int8 to int32 at a time; it now does sixteen, keeping the four-lane accumulator and the update order so max_t is unchanged. The direction byte was built with four and/orr pairs and a blend. NEON spells each as one BIT, which clang will not emit from the portable form: vbslq_u8(k, g, d) lowers to (k & g) | (~k & d), and for a constant splat known-bits proves ~k & d == d and folds it back. The instruction is written out for arm64, with the plain or/and and blendv kept elsewhere -- the flag sites need d's k bits already clear, the argmax site is a real blend, and sharing one form between them would be wrong off arm64. Together the left-alignment core loop goes from 77 to 67 instructions per 16 cells on aarch64. Measured on a 16-core Graviton4 at -t 16, mapping phase only: HiFi 10k 8.425s -> 7.480s, ONT 10k 9.909s -> 8.892s, WGS 1M pairs 9.907s -> 9.624s, Hi-C 1M pairs 73.766s -> 73.076s. Long reads gain most because bw_long leaves their gap fills effectively unbanded, so extd2 is most of their runtime. SAM output is byte-identical to r421 on 200k WGS pairs and 10k HiFi reads, and a differential harness comparing the old and new kernels directly reports 60000 pairs identical in both score and CIGAR at qlen 100, 150 and 250.
nh13
force-pushed
the
perf/ksw2-extension-kernels
branch
2 times, most recently
from
August 7, 2026 19:13
f7020b1 to
bc0384a
Compare
Owner
Author
|
Submitted upstream as lh3#64, from this same branch. Closing the fork-side review copy so there is one place to review. The branch was rebased onto current master (r421) and squashed to a single commit for submission; content is unchanged. The manifest entry now records |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Four kernel changes to the 128-bit
ksw_extd2/ksw_extz2, plus the direction-byteBITselects. Output is byte-identical to stock.This is the arm64 half of the extd2 story. It is orthogonal to lh3#20, which adds a new AVX2/AVX-512 path and a dispatcher while leaving the 128-bit kernel untouched — zero file overlap, and the two compose: x86 gets the wide path, arm64 gets a faster reference kernel.
What is here
6ac3a39vqtbl1q_u8)e85a6e9,58c5334vextinstead ofsrli/slli/ord63548cH[]16 int8 at a time rather than 464a5c6cBITLeft-alignment core loop, cumulative: 77 → 67 instructions per 16 cells.
On the last one: clang will not emit
BITfrom the portable spelling.vbslq_u8(k, g, d)lowers to(k & g) | (~k & d), and for a constant splat known-bits proves~k & d == d, folding it back toand/orr. Launderingkthrough an emptyasmblocks the fold and is worse — the backend materialises~kand emits three ops. Only a directBITgets the one-instruction form, so it is written out with a portable fallback off arm64.Two wrappers rather than one there, deliberately: the four flag sites OR into bits of
dknown clear, but theb > zsite is a true blend wheredalready holds 1, sod | (g & k)would yield 3 where the blend must yield 2. One shared OR-shaped fallback is correct on arm64 and silently wrong everywhere else.Measurements
c8g.4xlarge(Graviton4, 16 physical cores, no SMT),-t 16, mapping phase, SAM to/dev/null, 5 reps interleaved with order reversal, rep spread 0.01–0.49%:hifi-10kont-10kwgs-1Mhic-1MThe gain tracks each dataset's
ksw_extd2_sseshare of runtime, which is what a genuine extd2 change must do.ont-10kwas an out-of-sample check against a model built from the other three: 53.8% implied share against 54.9% profiled.Long reads dominate because
bw_longmakes their gap fills effectively unbanded, so extd2 is 55–65% of compute there against ~9% on short-read WGS.Correctness
A differential harness that builds the old and new kernels into one binary and compares them per pair: 60,000 pairs identical to stock in score AND cigar across qlen 100/150/250.
That gate had to be repaired first: it was running EXTEND rather than extd2, its workload pinned
e2 == eso the second affine piece was never selected, and it planted only 1 bp indels, which are always cheaper on the first piece. A deliberately corrupted0x40direction bit passed. These numbers come from the fixed gate.SAM output is byte-identical to stock
ebc59eaover 200k reads.x86
Unaffected. The
bitasm is inside#if defined(__ARM_NEON); compiling both variants for x86-64 (gcc 15.2,-O3 -msse4.2 -mpopcnt) gives identical instruction counts and disassembly differing only in the order of two constant materialisations.