Skip to content

Cut per-step overhead on narrow banded ends-free alignments - #59

Merged
cjfields merged 3 commits into
mainfrom
perf/narrow-step-overhead
Oct 18, 2026
Merged

cjfields merged 3 commits into
mainfrom
perf/narrow-step-overhead

Conversation

@cjfields

@cjfields cjfields commented Oct 18, 2026 •

Copy link
Copy Markdown
Member

Part of #31, prompted by HPCBio/dada2-rs#51's finding that begin-free gaps make each ends-free alignment about 3× slower. Details are in the #31 comment and PERFORMANCE_LOG.

Findings

  • The lead is confirmed: begin-free seeded diagonals stay in the wavefront. At 240 bp, 2%: 35 → 604 cells and 10 → 43 non-null steps per alignment.
  • Pruning them early doesn't help. I tried an initial upper bound for corrected mode's exact pruning: output was identical, but time was 2–18% worse. With a match reward, a cell's lower bound grows only |match| per diagonal from the end diagonal, against 18 per difference, so the seeded diagonals can't be ruled out until the search is nearly done. Not included here.
  • The time follows the step count: about 170 ns per extra non-null step, against under 1 ns per cell of kernel work. The per-step costs are spread out; the two clearest are fixed here.

Changes (separate commits)

  1. 494602c: init_ends fills of up to 8 offsets use volatile stores instead of slice::fill. fill lowered to a memset_pattern16 call per input per step, whose setup cost far more than the one or two stores.
  2. 9034dc5: apply_heuristic_cutoff returns at once for banded-static when the wavefront is inside the band. Compute already clamps to the band; only begin-free seeding reaches past it. That path doesn't use steps_wait, so skipping it changes nothing.
  3. 704a5e4: PERFORMANCE_LOG and PROGRESS notes.

Results

M2, median of 5, in ms, main (bef359a) → this PR. Output identical in every case.

data linear, begin off linear, begin on affine, begin off affine, begin on
240 bp, 2% 5.6 → 4.8 16.4 → 14.5 8.0 → 7.0 25.1 → 22.6
240 bp, 10% 16.6 → 14.1 47.6 → 41.1 23.6 → 20.2 72.2 → 60.7
1450 bp, 2% 58.8 → 52.9 190.3 → 172.8 79.6 → 70.3 257.5 → 226.8
1450 bp, 10% 164.4 → 149.5 479.1 → 437.5 220.0 → 193.9 659.5 → 586.0

This is 9–16% faster on dada2-like alignments. The utest and long gap-affine controls are at 0.99× and 0.96×. The 3× from begin-free remains: it's the step count of an exact WFA with seeded diagonals.

Validation

🤖 Generated with Claude Code, with edits by @cjfields

cjfields and others added 3 commits October 17, 2026 23:57
Each step widens its inputs' initialized range by a diagonal or two, and
slice::fill lowered that to a memset_pattern16 call whose setup cost far
more than the stores. Fills of up to 8 offsets now use volatile stores, so
LLVM doesn't turn the loop back into the call. On narrow banded ends-free
alignments (dada2-like) this is 7-14% faster; output is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
apply_heuristic_cutoff ran its general path every step. For banded-static
with the wavefront already inside the band (compute clamps to it; only
begin-free seeding reaches past it) the cutoff changes nothing and
doesn't use steps_wait, so return straight away. A few percent on narrow
banded alignments; output is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cjfields
cjfields merged commit b83c0ea into main Oct 18, 2026
17 checks passed
@cjfields
cjfields deleted the perf/narrow-step-overhead branch October 18, 2026 18:05
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.

1 participant