You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Synthetic 20 Mb genome, 2 M single-end 100 bp reads, --outSAMtype BAM Unsorted, macOS aarch64,
release build (with the batch-size fix from #222 already in, so this is the remaining ceiling):
--runThreadN
wall
user CPU
speedup
1
8.81s
11.67s
1.00x
2
4.82s
12.36s
1.83x
4
2.80s
13.49s
3.15x
8
2.43s
15.79s
3.63x
12
2.44s
17.19s
3.61x
Throughput stops improving after ~8 threads while user CPU keeps climbing and sys roughly doubles
(1.96s → 4.45s). On a 32- or 64-core machine the extra cores are being paid for and not used.
Where it goes
Same input, 8 threads, output disabled:
output
wall
BAM Unsorted
2.43s
None
1.60s
So the writer stage alone is ~34% of wall at 8 threads, and it is one thread: SAM record → BAM
encode → BGZF deflate, serially, for the whole run.
Gzipped input costs on top of that (8 threads, BAM out): 2.74s from .fq.gz vs 2.43s from plain
FASTQ, i.e. ~13%, again on a single decode thread.
Why
The align pipelines are decode(1 thread) → align(N rayon workers) → write(1 thread), joined by
bounded channels. The two ends are serial, so Amdahl caps the whole thing regardless of --runThreadN. The profile agrees: semaphore_wait_trap + __psynch_cvwait were ~16% of all
samples, i.e. workers parked waiting on the ends.
Only then consider reshaping the stage handoff itself (work-stealing over batches rather
than fixed producer/consumer threads). Do not start here: it is the largest change and the
smallest measured share.
Note
The numbers above are from a synthetic genome on a laptop; the shape (serial ends capping the
scaling) is architectural, but the exact crossover point should be re-measured on a real genome and
a real machine before sizing any of the fixes. That needs the benchmark harness from the
dependency sweep.
Measurement
Synthetic 20 Mb genome, 2 M single-end 100 bp reads,
--outSAMtype BAM Unsorted, macOS aarch64,release build (with the batch-size fix from #222 already in, so this is the remaining ceiling):
--runThreadNThroughput stops improving after ~8 threads while user CPU keeps climbing and
sysroughly doubles(1.96s → 4.45s). On a 32- or 64-core machine the extra cores are being paid for and not used.
Where it goes
Same input, 8 threads, output disabled:
BAM UnsortedNoneSo the writer stage alone is ~34% of wall at 8 threads, and it is one thread: SAM record → BAM
encode → BGZF deflate, serially, for the whole run.
Gzipped input costs on top of that (8 threads, BAM out): 2.74s from
.fq.gzvs 2.43s from plainFASTQ, i.e. ~13%, again on a single decode thread.
Why
The align pipelines are decode(1 thread) → align(N rayon workers) → write(1 thread), joined by
bounded channels. The two ends are serial, so Amdahl caps the whole thing regardless of
--runThreadN. The profile agrees:semaphore_wait_trap+__psynch_cvwaitwere ~16% of allsamples, i.e. workers parked waiting on the ends.
Options, in payoff order
noodles-bgzf0.50+ runs its multithreaded writer on its ownlocal pool with a supported
with_worker_count, so it no longer competes with rayon's globalpool. Biggest single lever, and the version bump is already in deps: noodles 0.113 -> 0.115 and noodles-bgzf 0.49 -> 0.51 (local thread pools, with_worker_count) #207 / chore(deps): bump noodles 0.113 -> 0.115, noodles-bgzf 0.49 -> 0.51 #211. Needs care: BGZF
block boundaries must stay byte-identical, or the change must be documented.
{C,V,,}BQor parallel readers forfastq) #97(parallel readers). For
.gzspecifically, note that a BGZF-compressed FASTQ can be inflated inparallel with the
noodles-bgzfwe already depend on, with no new dependency.than fixed producer/consumer threads). Do not start here: it is the largest change and the
smallest measured share.
Note
The numbers above are from a synthetic genome on a laptop; the shape (serial ends capping the
scaling) is architectural, but the exact crossover point should be re-measured on a real genome and
a real machine before sizing any of the fixes. That needs the benchmark harness from the
dependency sweep.