feat: add performance instrumentation and dynamic worker scaling
This change enhances observability and adaptability in the merge pipeline. Performance timing and debug logging are added to the De Bruijn graph and partition merge layers to track phase durations and pipeline metrics. The merge module replaces blocking receives with timed polls to sample CPU efficiency, dynamically spawning workers when utilization drops below a threshold. A new script is also introduced to parse merge debug logs and generate structured Markdown reports detailing throughput, phase breakdowns, and partition performance.
This commit is contained in:
@@ -7,8 +7,6 @@ use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||
use std::cell::RefCell;
|
||||
use std::fmt;
|
||||
use std::sync::atomic::{AtomicU8, Ordering};
|
||||
use std::time::Instant;
|
||||
use tracing::{debug, info};
|
||||
use xxhash_rust::xxh3::Xxh3Builder;
|
||||
|
||||
// ── Types ─────────────────────────────────────────────────────────────────────
|
||||
@@ -285,7 +283,6 @@ impl GraphDeBruijn {
|
||||
pub fn compute_degrees_and_mark_starts(&self) {
|
||||
// Pass 1: count right/left neighbors for each node
|
||||
|
||||
let t1 = Instant::now();
|
||||
self.for_each_node(|kmer, atomic| {
|
||||
let mut old = Node(atomic.load(Ordering::Relaxed));
|
||||
if old.is_visited() {
|
||||
@@ -295,20 +292,13 @@ impl GraphDeBruijn {
|
||||
}
|
||||
let (rc, rn) = count_neighbors(&kmer.right_canonical_neighbors(), &self.nodes);
|
||||
let (lc, ln) = count_neighbors(&kmer.left_canonical_neighbors(), &self.nodes);
|
||||
let mut node = Node(0); // reset all bits (visited=0, start=0)
|
||||
let mut node = Node(0);
|
||||
node.set_right(rc, rn);
|
||||
node.set_left(lc, ln);
|
||||
atomic.store(node.0, Ordering::Relaxed);
|
||||
});
|
||||
debug!(
|
||||
"[compute_degrees] pass 1 (degrees): {:?} — {} nodes",
|
||||
t1.elapsed(),
|
||||
self.nodes.len()
|
||||
);
|
||||
|
||||
// Pass 2: mark start nodes
|
||||
|
||||
let t2 = Instant::now();
|
||||
self.for_each_node(|kmer, atomic| {
|
||||
let mut node = Node(atomic.load(Ordering::Relaxed));
|
||||
if node.is_visited() {
|
||||
@@ -319,11 +309,6 @@ impl GraphDeBruijn {
|
||||
atomic.store(node.0, Ordering::Relaxed);
|
||||
}
|
||||
});
|
||||
debug!(
|
||||
"[compute_degrees] pass 2 (starts): {:?} — {} nodes",
|
||||
t2.elapsed(),
|
||||
self.nodes.len()
|
||||
);
|
||||
}
|
||||
|
||||
pub fn is_visited(&self, kmer: &CanonicalKmer) -> Option<bool> {
|
||||
@@ -391,7 +376,6 @@ impl GraphDeBruijn {
|
||||
let n2 = std::sync::atomic::AtomicUsize::new(0);
|
||||
|
||||
// Boucle unique : traiter les starts, recalculer les arités, recommencer
|
||||
let mut pass = 0usize;
|
||||
loop {
|
||||
let n_new = std::sync::atomic::AtomicUsize::new(0);
|
||||
|
||||
@@ -421,9 +405,7 @@ impl GraphDeBruijn {
|
||||
});
|
||||
|
||||
let n = n_new.load(Ordering::Relaxed);
|
||||
debug!("[for_each_unitig] pass {}: {} starts", pass, n);
|
||||
n_chains.fetch_add(n, Ordering::Relaxed);
|
||||
pass += 1;
|
||||
if n == 0 {
|
||||
break;
|
||||
}
|
||||
@@ -452,12 +434,6 @@ impl GraphDeBruijn {
|
||||
}
|
||||
}
|
||||
|
||||
debug!(
|
||||
chains = n_chains.load(Ordering::Relaxed),
|
||||
phase2 = n2.load(Ordering::Relaxed),
|
||||
total = n_chains.load(Ordering::Relaxed) + n2.load(Ordering::Relaxed),
|
||||
"unitig traversal complete"
|
||||
);
|
||||
}
|
||||
|
||||
/// Merge `other` into `self`.
|
||||
|
||||
Reference in New Issue
Block a user