Skip to content

Performance

Unless stated otherwise, numbers are suffix-array construction time and output is streamed to a hash sink. The current production-shaped measurements use 32 pinned physical cores on an AMD EPYC 9555. Every candidate emitted the same position count and output hash.

The current integration benchmark preserves the generalized, filtered suffix array that ruSTAR actually requests:

  • GENCODE Human v50 GRCh38 primary-assembly FASTA and comprehensive primary-assembly GTF;
  • sjdbOverhang=100 and all 698,597 deduplicated splice junctions;
  • 6,557,611,930 symbols, 1,397,582 segments, and STAR boundary ordering;
  • 6,176,694,310 retained ACGT-starting suffixes;
  • external-memory u64, 8,192 partitions, 32 physical cores.
ImplementationWallUser CPUPeak RSSPhase 1Phase 4
Pre-pass 0.7 baseline267.592 s7,622.88 s10,512,408 KiB106.847 s155.794 s
caps-sa 0.7.0172.953 s4,731.05 s9,169,892 KiB49.029 s118.743 s

The release is 35.4% faster and uses 12.8% less peak RSS. The retained changes fuse external sorting and distribution, prefetch upcoming text positions during merge, avoid transient record conversions, use task-local phase-1 ping-pong storage at high outer parallelism, and add a bounded coarse directory for large SegmentedText boundary sets.

On the focused chromosome-21 backbone plus every GENCODE-derived junction flank, stable measurements improved from 11.97–12.00 seconds to 7.77–7.80 seconds. All 359,616,038 emitted positions matched the reference.

The opt-in packed-prefix seed was measured against the released v0.7.0 baseline on the same complete fixture, with one warm-up followed by three interleaved measured runs. Values below are medians:

ConfigurationBuildUser CPUPeak RSSPhase 1Phase 4
Direct LCP198.924 s5,153.25 s9,142,300 KiB49.038 s144.923 s
Geometric memo only171.205 s4,684.95 s9,161,688 KiB49.034 s117.021 s
Packed seed only162.650 s3,987.38 s9,527,240 KiB12.204 s145.187 s
Packed seed + geometric memo134.618 s3,507.54 s9,536,692 KiB12.179 s117.265 s

The seed reduces phase 1 by 75.1% and improves the memoized ruSTAR configuration by 21.4%. Memoization changes phase 4 by 19.25% without the seed and 19.23% with it, confirming that the policies compose rather than competing for the same work. Every run emitted 6,176,694,310 positions with ordered hash e81c8f9881e322148741a23c92ae2000.

Peak RSS increased by 366–376 MiB (about 4.1%), matching the bounded (u64, u64) key records held by the 32 active phase-1 tasks. The dense ruSTAR alphabet required no ranked-text copy.

On chr21 without annotations, the direct build improved from a 1.291-second median to 0.934 seconds (27.7%). On the chr21 backbone plus every annotation- derived flank it improved from 7.709 to 6.025 seconds (21.8%), and the latter matched all 359,616,038 reference positions exactly.

The table above has geometric memoization enabled on both sides; do not add its isolated percentage to the 35.4% release improvement. Its separate direct A/B on the complete fixture measured:

PolicyWallUser CPUPeak RSS
Disabled291.710 s8,029.81 s10,672,468 KiB
Geometric defaults267.128 s7,607.24 s9,696,720 KiB

That is an 8.43% wall-time and 5.26% user-CPU improvement. Smaller or less repetitive inputs were neutral or only slightly positive, so the feature is opt-in. See Geometric LCP memoization for selection and tuning guidance.

The following older dataset compares caps-sa with the upstream C++ CaPS-SA reference on standard, unsegmented suffix arrays. It is retained as a generic implementation comparison; it is not the annotated ruSTAR workload above. Full methodology and the optimization ladder are in bench/README.md.

InputThreadscaps-saupstream C++
Yeast · 12 MB40.99 s3.94 s
Random DNA · 100 MB411.39 s12.17 s
Human GRCh38 · 3.1 GB3210.47 min10.93 min
GRCh38 peak RAM325.03 GB6.46 GB

On that historical GRCh38 input (32 threads, AMD EPYC 9575F), caps-sa was ~7% faster than upstream’s external-memory path and used ~23% less RAM.

build_in_memory_sample_sort skips disk entirely for hosts with the RAM to spare. On GRCh38 it benches at 11.64 min / 55 GB — about the same wall time as the external-memory path at roughly 10× the RAM. Reach for it only when disk I/O is the constraint; otherwise the external-memory path is both faster and far lighter.

Suffix comparison is the hot loop, and its cost is dominated by computing the longest common prefix of two suffixes. caps-sa selects one SIMD LCP backend per build — AVX-512BW hybrid → AVX2 → NEON → scalar — and threads it through the merge as a function pointer, so there is no per-comparison feature-detection overhead.

A single byte-level SIMD compare serves every symbol width through a byte-view dispatch. On a Zen 5 host, a 1 M-symbol long-LCP microbenchmark drops from ~200 ms scalar to 4–29 ms SIMD:

Symbol widthSpeedup vs scalar
u8~45×
u16~30×
u32~15×
u64~7×
  • subproblem_count (p) auto-targets ~65,536 selected positions per subarray, bounded below by the worker count and above by 8,192. More subproblems mean smaller per-partition merges (lower peak residency) at the cost of more bucket bookkeeping.
  • physical_file_count defaults to one temp file per worker. Raise it to reduce kernel write contention on fast local disks; lower it on networked filesystems with high metadata cost (or set CAPS_SA_N_PHYS).
  • work_dir should point at the fastest local scratch available for the external-memory path.
  • SegmentedText automatically adds a bounded coarse boundary directory for at least 256 segments. There is no tuning knob; the measured layout is capped at 8 MiB and avoids global boundary searches on generalized SAs with hundreds of thousands of short strings.
  • Geometric LCP memoization is disabled by default. Enable LcpMemoizationPolicy::geometric() only after an A/B shows enough reused long contexts to repay table lookups; retain the measured thresholds unless workload-specific profiling supports a change.

See ExtMemOpts for the full set of knobs.