Improve genomeGenerate multicore index build performance#2687
Improve genomeGenerate multicore index build performance#2687justinblethrow-cloud wants to merge 3 commits into
Conversation
Add a local benchmark script for genomeGenerate that records wall time, CPU and I/O samples, selected index-build settings, and per-stage timings from Log.out. This keeps performance validation reproducible while leaving STAR runtime behavior unchanged.
Use parallel traversal for SAindex generation and parallel bucket sorting for junction insertion indices. Keep deterministic output ordering while reducing the remaining annotation-heavy genomeGenerate stages.
692ceef to
fb426f8
Compare
|
Cleaned the branch history for review. The PR now has three logical commits: benchmark harness, SAindex/junction parallelization, and suffix-array construction improvements. The cleaned tree is identical to the previously pushed tree (verified by matching git tree hashes), so the benchmark and byte-identity results in the PR description still apply. Local validation after cleanup: |
Reduce genomeGenerate suffix-array build time by batching prefix-bin fills, retaining sorted chunks in RAM when memory allows, splitting very large prefix bins into ordered sub-bins, and using a comparator that can skip already-known prefix words. Optional SA sort profiling remains gated behind STAR_PROFILE_SA_SORT=1.
fb426f8 to
47e6051
Compare
|
Update after final hardening pass:
One broader skip-first-word safety guard was tested but not kept because it regressed suffix-sort time from the good ~238s profiled sort regime back to ~275s; the genomeGenerate buffer already has leading padding, and the retained guard is limited to the extra sub-bin prefix read. |
… 2.0) MSVC's OpenMP 2.0 requires signed integral index variables in `#pragma omp for`. The alexdobin#2687 ports used `uint` (64-bit) indices; switch the new parallel-for loops to int64 (genomeGenerate genome scans, genomeSAindex chunks, sjdbBuildIndex bucket count/scatter). No behavior change on GCC/Clang. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…2687), WASP-in-SAM (alexdobin#2617), CodeQL fix (#8) * chore(release): STAR-Cross 0.0.1 versioning, main branch, CodeQL third-party exclusion - Set project/binary version to "STAR-Cross 0.0.1_<hash>" (CMakeLists + VERSION fallback); update version_check test regex to 0.0.1. - Release workflow: name releases "STAR-Cross", auto-tag fallback 0.0.1_<sha>. - CI branch triggers master -> main (codeql.yml). - CodeQL: mark cpp-httplib include as SYSTEM (CodeQL skips system headers — the reliable fix for the cpp/non-https-url alert in the #include'd httplib.h, which paths-ignore cannot filter for C/C++); broaden config globs to **/_deps/**. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat: allow WASP vW:i tag in SAM output (upstream PR alexdobin#2617) Emit the vW WASP filtering tag in the SAM/CRAM path and drop the BAM-only restriction on --waspOutputMode and the vW attribute. Emission is scoped to ATTR_vW only (the upstream patch incorrectly shared it with vG/vA). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * perf: multicore genomeGenerate suffix-array build (upstream PR alexdobin#2687) Port the parallel prefix-bucketed suffix-array chunk sort (with sub-binning, optional in-memory chunk retention, and a skip-first-word comparator fast-path) from upstream PR alexdobin#2687. Reconciled with the fork: - funCompareSuffixesFromWord uses the big-endian-safe loadUintLE loads. - sjdbSortBucket reformulated without __uint128 (MSVC has no native 128-bit); the bucket mapping stays monotonic in the key, so the total order — and thus the final index — is unchanged. - SA chunk packing keeps the in-memory path and the binary-mode disk fallback. Index output is byte-identical to the previous builder; a new CI job (validate-genome-index) builds the main baseline and this branch and diffs the SA/SAindex/Genome across 1 vs 16 threads and a low-RAM multi-chunk layout. build.yml CI branch triggers also move master -> main. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: STAR-Cross 0.0.1, CRAM/big-endian/ARM, genomeGenerate perf, WASP-in-SAM Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(win): use signed loop indices in OpenMP parallel-for (MSVC OpenMP 2.0) MSVC's OpenMP 2.0 requires signed integral index variables in `#pragma omp for`. The alexdobin#2687 ports used `uint` (64-bit) indices; switch the new parallel-for loops to int64 (genomeGenerate genome scans, genomeSAindex chunks, sjdbBuildIndex bucket count/scatter). No behavior change on GCC/Clang. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…am PR alexdobin#2687) byteOrder.h routes genome/SA/packed-array access through loadUintLE/storeUintLE; little-endian keeps the native single-instruction load, only big-endian compiles take the portable path. Port alexdobin#2687's parallel prefix-bucketed SA build, reconciled with the byte-order-safe comparator and MSVC (no __uint128; signed OpenMP indices). Index output is byte-identical (verified in CI). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rk scripts doctest coverage for the little-endian byte helpers (exercises the big-endian algorithm on the LE runner) and a script that proves the alexdobin#2687 SA build yields a byte-identical index vs baseline across thread counts and chunk layouts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… (upstream alexdobin#2657) (#10) Root cause: in sjdbBuildIndex()'s SAi-insertion loop, the inner while increments iSJ and then calls funCalcSAi(Gsj + indArray[2*iSJ+1], iL) WITHOUT re-checking iSJ < nInd. When the scan advances past the last junction, iSJ reaches nInd and reads the sentinel offset indArray[2*nInd+1] == (uint64)-999, i.e. funCalcSAi(Gsj - 999, ...) — an out-of-bounds read ~999 bytes before the Gsj allocation. On most heap layouts that is harmless garbage (the loop then exits via the sentinel key), but where Gsj-999 is unmapped it segfaults. This matches the report exactly: indArray[2*iSJ+1] == 18446744073709550617 == (uint64)-999. Fix: break out of the loop when iSJ reaches nInd, before dereferencing the sentinel. This yields the loop's intended "no matching junction" outcome (the else branch resets iSJ), matching the sibling loop below which already guards funCalcSAi with iSJ<nInd. Behavior is unchanged for runs that never reach the sentinel, and it also removes the original's latent risk of funCalcSAi(garbage) matching and writing a sentinel-based index. Adds an AddressSanitizer CI job (asan-sjdb) exercising the sjdb-insertion path, giving layout-independent coverage of this read (and of the alexdobin#2687 sort rewrite). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Summary
genomeGeneratemulti-core throughput for large referencesextras/tests/scripts/benchmarkGenomeGenerate.shto capture wall time, CPU/I/O samples, and genomeGenerate stage timingsBenchmark notes
--genomeSAindexNbases 14,--genomeChrBinNbits 18,--limitGenomeGenerateRAM 300000000000results/full_chm13_prfinal_20260520fill_count_seconds=1.629,fill_scatter_seconds=36.699,sort_seconds=237.783,finalize_seconds=1.788Genome,SA,SAindex, chromosome metadata, andsjdbList.out.tabagainst the prior accepted optimized buildValidation
make -C source STARbenchmarkGenomeGenerate.shruns at 1 and 16 threadsSTAR_PROFILE_SA_SORT=1Notes for review
extras/tests/scripts/.STAR_PROFILE_SA_SORT=1; normal runs do not emit those profiling details.