Skip to content

perf: buffer all output file writers (2.4x wall clock) - #139

Open
BenjaminDEMAILLE wants to merge 1 commit into
seqeralabs:mainfrom
BenjaminDEMAILLE:perf/buffer-output-writers
Open

perf: buffer all output file writers (2.4x wall clock)#139
BenjaminDEMAILLE wants to merge 1 commit into
seqeralabs:mainfrom
BenjaminDEMAILLE:perf/buffer-output-writers

Conversation

@BenjaminDEMAILLE

@BenjaminDEMAILLE BenjaminDEMAILLE commented Aug 13, 2026

Copy link
Copy Markdown

What

Wraps the remaining 28 output-file writers in BufWriter. Nine call sites already did this; the rest wrote line by line straight into a std::fs::File, i.e. one write(2) syscall per line.

Why it matters

Several outputs scale with input size:

file lines on the benchmark input
rseqc/inner_distance/*.inner_distance.txt 1,000,000 (one per read pair)
rseqc/tin/*.tin.xls 150,001 (one per transcript)
rseqc/junction_annotation/*.junction.{xls,bed,Interact.bed} 73,663 each
featurecounts/*.tsv, dupradar/*_dupMatrix.txt 60,001 each (already buffered)

Measurement

Synthetic 4M-read coordinate-sorted BAM (24 contigs, random sequences), 1.2M-line GTF / 60k genes, aarch64 macOS, --threads 4. Five interleaved runs per binary, medians:

real user sys
main 23.16 s 24.03 s 13.45 s
this PR 9.72 s 23.20 s 0.70 s

2.4x wall clock. System time falls 95% — that is the syscall traffic disappearing. User time is unchanged, which is the expected signature: the same work, far fewer syscalls.

Correctness

Each converted writer gets an explicit flush()? before returning, so a failing final flush is reported instead of being swallowed by BufWriter's Drop.

All deterministic outputs are byte-identical before and after. cargo test --release passes (200 + 12 + 18 + 2).

Pre-existing issue found while verifying

Three outputs are not reproducible run-to-run on main alone — two runs of the same binary on the same input differ:

  • dupradar/*_duprateExpDens.svg (and the matching .png)
  • qualimap/qualimapReport.html
  • qualimap/rnaseq_qc_results.txt

The differences are pure line/element reordering of equal-valued entries, so no number changes, but the files do not hash-match. It looks like HashMap iteration order reaching output (compute_bias in qualimap/output.rs has a comment acknowledging a related tie-break). Unrelated to this PR — happy to open a separate issue.


🤖 Generated with Claude Code

Part of #143.

Most of the writer functions wrote line by line straight into a
`std::fs::File`, which means one `write(2)` syscall per line. Nine call
sites already wrapped the file in a `BufWriter`; the other 28 did not, so
the behaviour was inconsistent rather than deliberate.

It matters because several outputs are large and scale with input size:
inner_distance's detail file emits one line per read pair (1,000,000 lines
on the benchmark input below), the TIN table one line per transcript
(150,001), and each of the three junction_annotation files one line per
junction (73,663).

Measured on a synthetic 4M-read coordinate-sorted BAM (24 contigs, random
sequences) with a 1.2M-line GTF (60k genes), aarch64 macOS, --threads 4,
5 interleaved runs, medians:

  before: real 23.16 s   user 24.03 s   sys 13.45 s
  after:  real  9.72 s   user 23.20 s   sys  0.70 s

2.4x wall clock. System time drops by 95%, which is the syscall traffic
disappearing; user time is unchanged, as expected for a change that does
no less work, only fewer syscalls.

Each converted writer gets an explicit `flush()?` before returning so a
failed final flush is reported rather than swallowed by `BufWriter`'s drop.

All deterministic outputs are byte-identical before and after.
`cargo test --release` passes (200 + 12 + 18 + 2).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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