bam: --outBAMsortingBinsN spills the coordinate sort to disk bins - #151
Open
BenjaminDEMAILLE wants to merge 2 commits into
Open
bam: --outBAMsortingBinsN spills the coordinate sort to disk bins#151BenjaminDEMAILLE wants to merge 2 commits into
BenjaminDEMAILLE wants to merge 2 commits into
Conversation
`--limitBAMsortRAM` was a threshold the sort died on, not a bound it respected:
every record stayed resident until `finish()`, and exceeding the limit aborted
the run with a message telling the user to raise it or give up on sorting.
Now, when a bound is set and the buffer exceeds it, records are partitioned by
reference sequence into `--outBAMsortingBinsN` bins written to temporary BAMs,
then read back one bin at a time, sorted and appended. Because the bins are
coordinate-disjoint and already in order relative to one another, no k-way
merge is needed. Only one bin is resident during the sort, so peak usage is the
largest bin rather than the whole run. Unmapped records sort last and get their
own bin.
Spilling is off unless it buys something: it needs both a non-zero
`--outBAMsortingBinsN` and a `--limitBAMsortRAM` that the estimate actually
exceeds. Without a bound there is nothing to respect and temporary files would
be pure cost. `--outBAMsortingBinsN 0` disables it outright.
The bin files are written uncompressed: they are read back immediately, so
compressing them would cost time and save nothing.
Verified on 1161 records that the binned and in-memory paths produce
byte-identical decoded BAM:
in-memory b52a23436e3e939e98160dffed89c1448c5da4da
binned b52a23436e3e939e98160dffed89c1448c5da4da
An earlier version of this partitioned in memory, which bounded the sort's
working set but not residency — every bucket was live at once. The comment
claiming reduced peak usage would have been false, so it spills for real.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The test asserted the old contract: exceeding the bound was fatal. With binning it is not, because the sort can now respect the bound instead of dying on it. Both halves are covered: with bins available the sort succeeds, and with --outBAMsortingBinsN 0 there is no way to honour the bound, so the run still stops rather than quietly using more memory than it was allowed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Jul 29, 2026
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.
--outBAMsortingBinsNspills the coordinate sort to disk bins.What changed
When
--limitBAMsortRAMis set and the buffer exceeds it, records are partitioned by reference sequence into--outBAMsortingBinsNbins written to temporary BAMs, then read back one bin at a time, sorted and appended.The bins are coordinate-disjoint and already ordered relative to one another, so no k-way merge is needed: concatenating the sorted bins is the sorted output. Only one bin is resident during the sort, so peak usage is the largest bin rather than the whole run. Unmapped records sort last and get their own bin.
Spilling requires both a non-zero
--outBAMsortingBinsNand a--limitBAMsortRAMthe estimate exceeds; without a bound there is nothing to respect and temporary files would be pure cost.--outBAMsortingBinsN 0disables spilling, and exceeding the limit is then still fatal — there is no way to honour the bound, so the run stops rather than quietly using more memory than allowed.Bin files are written uncompressed: they are read back immediately, so compressing them would cost time and save nothing.
Why
--limitBAMsortRAMwas a threshold the sort died on, not a bound it respected. Every record stayed resident untilfinish(), and exceeding the limit aborted the run.Verification
Binned and in-memory paths produce byte-identical decoded BAM on 1161 records:
561 lib + 26 integration green, clippy
-D warningsclean,cargo fmt --checkclean, MSRV 1.89.Related negative result
The other half of this theme was dropped. Swapping to
noodles_bgzf::MultithreadedWritermeasured ~37% slower at every size tested, up to 44 MB of BAM output. The premise was wrong:noodles-bgzfis declared here with itslibdeflatefeature and both writers go through the samedeflate::encode, so the single-threaded path was not the naive-deflate bottleneck the STAR-rs comparison describes. Numbers on #143. This PR contains none of that work.🤖 Generated with Claude Code