Skip to content

Optimize hts_Stats hot path - #273

Open
msettles wants to merge 6 commits into
perf/benchmark-speedupsfrom
perf/stats-hotpath-speedups
Open

Optimize hts_Stats hot path#273
msettles wants to merge 6 commits into
perf/benchmark-speedupsfrom
perf/stats-hotpath-speedups

Conversation

@msettles

@msettles msettles commented Jul 1, 2026

Copy link
Copy Markdown
Member

Summary

  • cache qual-offset once per StatsCounters instance instead of reading from variables_map for every base
  • replace per-cycle std::vector rows with fixed-width std::array rows for base and quality matrices
  • use a compact base lookup table in the inner loop instead of a switch
  • preserve existing public base counters and JSON output format

Benchmarks

Sample: /Users/biomsettles/projects/Archive/Alliance_MiSeq_Bacterial_Data/00-Original/sample1 with 805,327 paired fragments.

Same command, stdout redirected to /dev/null:

  • parent branch hts_Stats: 15.16s real, 15.54s user, 1.68s sys
  • this branch hts_Stats: 3.70s real, 3.69s user, 1.64s sys

That is roughly a 4.1x speedup for the standalone hts_Stats run on this sample.

Stacked pipeline timing:

  • parent PR pipeline with --compression-level 1: 16.89s real
  • this branch pipeline hts_Stats | hts_SuperDeduper | hts_LengthFilter --compression-level 1: 5.32s real

That is roughly a 3.2x speedup for this streamed pipeline after the stats bottleneck is removed.

Validation

  • cmake --build build -j8
  • ctest --test-dir build --output-on-failure (13/13 passed)
  • cd regression && ./regression.sh ../build test
  • baseline and optimized fragment/base-composition counters matched on the benchmark sample
  • gzip -t passed for the benchmark pipeline output

@msettles
msettles force-pushed the perf/benchmark-speedups branch from 2cd209a to 44d4bc0 Compare July 1, 2026 21:48
@msettles
msettles force-pushed the perf/stats-hotpath-speedups branch from 427cd26 to c27814e Compare July 1, 2026 21:48

@joe-angell joe-angell left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the improvements to counting stats but I see an issue with the do_read1_read2_files function. It seems to be redundant as it loads the input and does all the counting, then do_app does the same thing over again afaik. Maybe I'm missing something, it's been a while since I looked at this code.

Comment thread hts_Stats/src/hts_Stats.h
const bool no_output = vm["no-output"].as<bool>();
WriterHelper writer(pe, se, false);
if (vm["number-of-threads"].as<size_t>() > 1) {
do_parallel_stats(reader, no_output ? nullptr : &writer, counters, vm);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

number-of-threads defaults to 1, do we have a test for this code path?

Comment thread hts_Stats/src/hts_Stats.h
}

static bool load_fastq_record(std::istream &input, std::string &seq, std::string &qual) {
std::string id;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit concerned about duplicating the code to read records

Comment thread hts_Stats/src/hts_Stats.h
bool do_read1_read2_files(const std::vector<std::string> &read1_files, const std::vector<std::string> &read2_files,
std::shared_ptr<OutputWriter>, std::shared_ptr<OutputWriter>,
StatsCounters& counters, const po::variables_map &vm) {
if (!vm["no-output"].as<bool>()) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this function should be removed. When multithreaded this will read the input files and construct the counters, then when do_app is called the files will be opened and read again, not sure how this could even work when piped...

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.

2 participants