Skip to content

Zero-copy record processing with seq_io (replaces bio) - #17

Open
werner291 wants to merge 2 commits into
SciLifeLab:mainfrom
werner291:wk/zerocopy-io
Open

Zero-copy record processing with seq_io (replaces bio)#17
werner291 wants to merge 2 commits into
SciLifeLab:mainfrom
werner291:wk/zerocopy-io

Conversation

@werner291

@werner291 werner291 commented Jun 25, 2026

Copy link
Copy Markdown

Swaps the FASTQ parser from bio to seq_io and writes records as raw byte segments instead of building owned Records, dropping the per-record allocations and the redundant seq/qual copies (and the bio/itertools deps). Output is byte-identical on well-formed input across header/inline, with and without --correct_numbers, plain and gzipped.

Speed: ~4x with uncompressed output; with gzip it's only a few percent at -t9 (gzip dominates). But the bio transform is a serial bottleneck that stops scaling once you add threads, while the seq_io path keeps scaling with cores (i.e. gzip is left to work faster).

One behavior change: seq_io rejects records where sequence and quality differ in length, which bio passed through silently. That surfaced that the tests/seqdata fixtures are malformed (102-base seq, 101-char qual in every record), so the content tests fail against them. Reject malformed FASTQ (I'd lean yes), or keep the lenient pass-through? I'll fix the fixtures/tests once you decide.

Read FASTQ with seq_io's borrowing parser and write spliced records as raw
byte segments, instead of building owned records with the bio crate. This
removes the per-record allocation and the redundant seq/qual copies on the
read-edit-write path.

- file_io: read_fastq returns a seq_io reader; OutputFile is a buffered byte
  sink with an explicit gzip finish() (gzp needs it; Drop can truncate).
- read_editing: umi_to_record_* replaced by segment writers for header and
  inline modes.
- umi_external: lockstep loop over the three readers, no owned records.

Output is byte-identical to the previous version on well-formed input across
header/inline/-c and plain/gz I/O. seq_io additionally validates that seq and
qual have equal length, so malformed records that bio silently passed through
are now rejected.
@werner291
werner291 marked this pull request as draft June 25, 2026 16:21
@werner291
werner291 marked this pull request as ready for review June 25, 2026 16:24
read1.fq and read2.fq had 102-base sequences with 101-char quality
strings. bio tolerated it; seq_io rejects the mismatch, failing the
suite at parse time. Padded each quality string by one and regenerated
the affected goldens in tests/results/ (gzipped ones from the tool's
output, verified against the plain goldens). Test data only.
@werner291

Copy link
Copy Markdown
Author

The failing tests were just malformed fixtures: read1.fq/read2.fq had 102-base sequences with 101-char quality lines. bio passed that through; seq_io rejects it. Padded the quality strings and regenerated the goldens. Suite is green now.

Separate question, your call: keep seq_io's strict rejection of malformed FASTQ, or restore bio's lenient pass-through? I lean strict, the per-record error is what lets a maintainer point back at the bad input instead of debugging it three steps downstream. Happy either way.

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