Repository navigation
Conversation
Contributor
There was a problem hiding this comment.
Note
Copilot was unable to complete this review at the selected tier due to model policy or plan restrictions.
🟡 Changes recommended
Changes are still needed because the review found two moderate correctness issues: a Javadoc/behavior mismatch in Accumulator.addEncodedBase() and a race in CountingConcurrentQueue.offer() that can under-count size() and break back-pressure or empty-wait logic.
2 open issues
What changed in this PR
This PR optimizes qsnp’s hottest path (base accumulation and queue back-pressure) to reduce allocations, improve cache locality, and cut GC overhead, with additional pipeline-side changes to better amortize accumulator growth and reduce scanning/spinning.
Changes:
- Reworks
Accumulator/AccumulatorUtilsto use primitive arrays and split per-read/per-base encoding to reduce per-base overhead and allocations. - Updates qsnp
Pipelineconsumer/cleaner logic (adaptive initial capacities, fewer iterator allocations, reduced rescans/spins, contig-scoped compound SNP detection). - Introduces
CountingConcurrentQueueto provide O(1)size()for producer back-pressure, plus adds/updates unit tests and runtime guidance (Parallel GC).
| File | Description |
|---|---|
| qsnp/test/org/qcmg/snp/PipelineTest.java | Adds regression tests for contig-scoped compound SNP handling and new consumer encoding/capacity behavior. |
| qsnp/test/org/qcmg/snp/CountingConcurrentQueueTest.java | Adds tests for queue semantics, iterator immutability, and concurrent producer/consumer behavior. |
| qsnp/src/org/qcmg/snp/Pipeline.java | Applies performance improvements in consumer/cleaner loops, contig-scoped compound SNP logic, and uses the new queue for back-pressure. |
| qsnp/src/org/qcmg/snp/CountingConcurrentQueue.java | Adds a counted wrapper around ConcurrentLinkedQueue to avoid linear-time size(). |
| qcommon/test/org/qcmg/common/util/AccumulatorUtilsTest.java | Adds tests ensuring new split-encoding matches legacy encoding and allocation-free helper behavior stays correct. |
| qcommon/test/org/qcmg/common/model/AccumulatorTest.java | Adds tests for raw-array growth, list views, setData copying, and failed-filter storage growth. |
| qcommon/src/org/qcmg/common/util/AccumulatorUtils.java | Optimizes encoding and hot helpers to avoid allocations and use raw-array reads. |
| qcommon/src/org/qcmg/common/model/Accumulator.java | Replaces TLongArrayList storage with raw arrays and adds addEncodedBase + lightweight list views. |
| qbamfilter/src/org/qcmg/qbamfilter/filter/CigarFilter.java | Switches to index-based CIGAR iteration to avoid iterator allocations. |
| docs/qsnp/qsnp_v2_0.md | Documents Parallel GC and heap sizing recommendations for WGS tumour/normal pairs. |
| adama/bin/qsnp | Defaults launcher to -XX:+UseParallelGC for improved WGS throughput and reduced CPU usage. |
Review effort: Lite
Comment on lines
+216
to
+217
| * @param initialCapacity capacity (in longs - each base uses 2) of the data list if this is the first base added to this Accumulator | ||
| * @return true if this was the first base added to this Accumulator |
Comment on lines
+33
to
+38
| @Override | ||
| public boolean offer(E e) { | ||
| queue.offer(e); // always returns true (or throws NPE for null) | ||
| count.increment(); | ||
| return true; | ||
| } |
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.

Summary
qsnp is 22% faster on a whole-genome tumour/normal pair (about 115x tumour, 40x normal). Both runs below used the same node, JDK 25 and input, and produced identical VCFs:
-XX:+UseParallelGCWith Parallel GC, the JVM also used about half the CPU: about 5 cores on average instead of about 11 under G1.
Background
Profiling with JFR (
-XX:+DebugNonSafepoints, so the results aren't skewed towards safepoints) showed:Accumulators, which is memory-latency bound.Accumulators live for a few seconds, long enough to be promoted to the old generation before the cleaner frees them, so G1 keeps copying them.Changes
Accumulator/AccumulatorUtils(qcommon)long[]with a size counter instead of aTLongArrayList. That saves an object and a pointer dereference per position.addEncodedBase(readNameHash, encodedBase, initialCapacity)adds a base that is already encoded.encodeReadBitsandencodeBasesplit the encoding so the per-read part (strand and start position) is computed once per read, not once per base.getData()andgetFailedFilterXCount()now return lightweightTLongListviews over the arrays. Existing callers still work, but the hot paths inAccumulatorUtilsreadgetDataArray()/getDataSize()directly.getUniqueBasesMaskand related helpers no longer allocate.Pipeline(qsnp)Accumulators to the mean depth it observes (1.25 × mean depth, kept between 16 and 1024 longs). This avoids repeated array growth at 100x+. Changes are logged.Accumulatoris looked up once per aligned block, and bases are added with the per-read encoding computed in advance.CigarFilter. This removes anArrayList$Itrallocation per record.minStartPositionis written with a release store instead of a full volatile write.cleanedUpToPosition). Previously it rescanned up to 10M slots each time.adjacentAccumulatorsis cleared at the end of each contig.New
CountingConcurrentQueue(qsnp)ConcurrentLinkedQueuewrapper whosesize()reads aLongAddercounter, so it takes constant time.ConcurrentLinkedQueue.size()walks every node, and the producers' back-pressure check calls it on queues holding up to millions of records.Launcher and docs
adama/bin/qsnpnow runs with-XX:+UseParallelGC.docs/qsnp/qsnp_v2_0.mdrecommends Parallel GC and a large heap (60 GB in testing) for WGS pairs.GC comparison (10-minute JFR window, 60 GB heap)
Parallel GC's stop-the-world pauses briefly use all available cores. If that matters on shared nodes, add
-XX:ParallelGCThreads=N.Testing
AccumulatorTest: array growth beyond the initial capacity, views matching the underlying arrays,setDatacopying, and failed-filter growth.AccumulatorUtilsTest: the new encoding and the allocation-free helpers.PipelineTest: consumer output matches the oldaddBasepath, failed-filter lists included.CountingConcurrentQueueTest: a concurrent producer/consumer with 1M items, among others.Notes
stdQsnpruns on older nodes such as hpcnode018 took 3 h 42 m for the same sample, so node choice is also a large factor there.adama/bin/qsnpdoesn't set-Xmx, so the JVM defaults to 25% of the memory it can see. Make sure the Cromwell task requests enough memory, or set the heap explicitly.🤖 Generated with Claude Code