perf(depth): stream samtools depth into numpy arrays (6.3x faster, -133 MB) - #1
Open
sanjaynagi-eit wants to merge 1 commit into
Open
perf(depth): stream samtools depth into numpy arrays (6.3x faster, -133 MB)#1sanjaynagi-eit wants to merge 1 commit into
sanjaynagi-eit wants to merge 1 commit into
Conversation
get_depths_from_bam read the whole `samtools depth` output into one decoded python string, split it into a list of strings, and scattered it into a python list of ints per base per contig. collate_depths then ran statistics.mean/stdev over those lists. For a bacterial chromosome at ONT depth that is a few hundred MB of transient python objects and several seconds of pure-python work, repeated for the short and long bam in hybrid mode with both dicts alive at once. Stream the depth output through the pandas C parser in bounded chunks and scatter it into preallocated int32 arrays (4 bytes/base), then compute the summary with numpy. np.std needs ddof=1 to match statistics.stdev, which is the sample standard deviation; contigs shorter than 2 bases still report NA, which the old code reached via StatisticsError. Measured on a real 2.8 Mb ONT assembly (29 MB sorted bam): get_depths_from_bam 1.67s -> 0.95s collate_depths 4.47s -> 0.02s total 6.14s -> 0.97s (6.3x) peak RSS 410 MB -> 277 MB with byte-identical summary output. An unknown contig - one present in the bam but not in contig_lengths - now raises with a message naming it. The old dict indexing raised a bare KeyError there, and the bam is built by mapping against exactly those contigs, so it means the fasta and the bam disagree and should not be silently dropped.
sanjaynagi-eit
force-pushed
the
perf/depth-numpy-streaming
branch
from
August 13, 2026 22:11
73d27c0 to
02f8a04
Compare
Owner
Author
|
Updated after a self-review pass. A contig present in the bam but absent from |
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.
Problem
get_depths_from_bamwas the single largest chunk of pure-python work in a plassembler run:sp.check_outputcaptured the entiresamtools depthoutput as one decoded python string, then.splitlines()doubled it as a list of strings.listof ints per base per contig ([0] * repLength).collate_depthsthen ranstatistics.mean/statistics.stdevover those lists. Thestatisticsmodule works in exact rationals and is very slow at this scale.In hybrid mode this happens twice (short and long bam) with both dicts alive simultaneously.
Change
samtools depthviaPopenand parse it with the pandas C parser in bounded 2M-row chunks, scattering into preallocatednp.int32arrays (4 bytes/base).np.mean/np.std/np.percentile.samtools depthexit instead of silently returning zeros.Preserving behaviour exactly
np.stdis called withddof=1:statistics.stdevis the sample standard deviation, numpy defaults to the population one.NAfor all four columns, which is what the old code produced viaStatisticsError.collate_depthsstill accepts plain python lists, so existing callers and tests are unaffected.Measurements
Real ONT isolate, 2.8 Mb Flye chromosome, 29 MB sorted bam (each implementation in a fresh process):
get_depths_from_bamcollate_depthsThe summary dataframe is byte-identical between the two implementations. End-to-end on the same sample, peak python RSS drops 417 MB -> 280 MB.
Tests
tests/test_deterministic.pygains four cases: numpy arrays and python lists produce identical summaries, the sample-vs-population stdev distinction is pinned, sub-2-base contigs report NA, andget_depths_from_bamis checked against a hand-built bam including a contig with no alignments and an alignment-free bam.All 73 non-slow tests pass.