From f96043b5030fe3bd2da9864bf357802648ff2ba9 Mon Sep 17 00:00:00 2001 From: amkram Date: Mon, 13 Jul 2026 13:55:58 -0700 Subject: [PATCH] perf(placement): prefetch the table the scoring loop actually probes computeChildMetrics prefetched seedFreqInReads and seedInverseGenomeCounts, but the hot per-seed loop reads neither -- it probes logReadCounts, which was never prefetched. So every per-seed lookup missed cache while bandwidth was spent prefetching two tables that are never read here. Prefetch logReadCounts instead (and drop the two dead prefetches). A/B (single-thread): TB place -17% at t1 (5.63 -> 4.66s), -6% at t8, -4% at t16; SARS place neutral (read-processing-bound, tables cache-resident). Placement output byte-identical; unit + e2e + examples pass. Found re-profiling the memory-bound scoring loop -- a single-socket taskset test ruled out NUMA, pointing at cache/bandwidth, and the prefetch/lookup mismatch was the culprit. --- src/placement.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/placement.cpp b/src/placement.cpp index ea9db55d..b7ffae86 100644 --- a/src/placement.cpp +++ b/src/placement.cpp @@ -268,8 +268,9 @@ void placement::NodeMetrics::computeChildMetrics(placement::NodeMetrics& childMe const size_t initialPrefetchCount = std::min(numChanges, PREFETCH_DISTANCE); for (size_t i = 0; i < initialPrefetchCount; ++i) { __builtin_prefetch(&H[i], 0, 0); - state.seedFreqInReads.prefetch(H[i]); - state.seedInverseGenomeCounts.prefetch(H[i]); + // Prefetch the table this loop actually probes (logReadCounts), not the + // read-processing tables that are never read here. + state.logReadCounts.prefetch(H[i]); } // Batch for instruction pipelining and cache utilization @@ -281,8 +282,7 @@ void placement::NodeMetrics::computeChildMetrics(placement::NodeMetrics& childMe if (i + PREFETCH_DISTANCE < numChanges) [[likely]] { const uint64_t nextHash = H[i + PREFETCH_DISTANCE]; __builtin_prefetch(&H[i + PREFETCH_DISTANCE], 0, 0); - state.seedFreqInReads.prefetch(nextHash); - state.seedInverseGenomeCounts.prefetch(nextHash); + state.logReadCounts.prefetch(nextHash); } const uint64_t seedHash = H[i];