jugar_probar/coverage/thread_local.rs
1//! Thread-Local Counter Buffering (Muda Elimination)
2//!
3//! Per spec ยง5.3.1: Workers increment local registers and flush to global
4//! counters only upon block exit or checkpoint. This reduces bus contention
5//! from O(N) to O(B) where N is instructions and B is block transitions.
6
7use super::BlockId;
8
9/// Thread-local counter buffer (Kaizen: eliminates atomic contention)
10///
11/// Instead of atomic increments on every block hit, counters are buffered
12/// locally and flushed periodically. This dramatically reduces cache
13/// coherence traffic in parallel coverage collection.
14#[derive(Debug)]
15pub struct ThreadLocalCounters {
16 /// Local counter buffer (one per block)
17 local: Vec<u64>,
18 /// Flush threshold (blocks executed before sync)
19 flush_threshold: usize,
20 /// Blocks since last flush
21 blocks_since_flush: usize,
22 /// Number of flushes performed
23 flush_count: usize,
24}
25
26impl ThreadLocalCounters {
27 /// Create new thread-local counters for the given number of blocks
28 ///
29 /// Uses a default flush threshold of 1000 blocks.
30 #[must_use]
31 pub fn new(block_count: usize) -> Self {
32 Self {
33 local: vec![0; block_count],
34 flush_threshold: 1000,
35 blocks_since_flush: 0,
36 flush_count: 0,
37 }
38 }
39
40 /// Create with a custom flush threshold
41 ///
42 /// Lower thresholds mean more frequent flushes (more accurate but slower).
43 /// Higher thresholds mean fewer flushes (faster but less accurate intermediate state).
44 #[must_use]
45 pub fn with_flush_threshold(block_count: usize, threshold: usize) -> Self {
46 Self {
47 local: vec![0; block_count],
48 flush_threshold: threshold,
49 blocks_since_flush: 0,
50 flush_count: 0,
51 }
52 }
53
54 /// Increment counter locally (no atomic, no cache coherence traffic)
55 ///
56 /// This is the hot path - must be as fast as possible.
57 #[inline(always)]
58 pub fn increment(&mut self, block: BlockId) {
59 let idx = block.as_u32() as usize;
60 if idx < self.local.len() {
61 self.local[idx] += 1;
62 self.blocks_since_flush += 1;
63
64 // Amortize flush cost over many increments
65 if self.blocks_since_flush >= self.flush_threshold {
66 self.internal_flush();
67 }
68 }
69 }
70
71 /// Get the current local count for a block (before flush)
72 #[inline]
73 #[must_use]
74 pub fn get(&self, block: BlockId) -> u64 {
75 let idx = block.as_u32() as usize;
76 self.local.get(idx).copied().unwrap_or(0)
77 }
78
79 /// Flush local counters and return the counts
80 ///
81 /// Returns the accumulated counts and resets the local buffer.
82 #[must_use]
83 pub fn flush(&mut self) -> Vec<u64> {
84 let result = self.local.clone();
85 self.local.fill(0);
86 self.blocks_since_flush = 0;
87 self.flush_count += 1;
88 result
89 }
90
91 /// Internal flush that just resets state (for automatic threshold flush)
92 fn internal_flush(&mut self) {
93 // In a real implementation, this would atomically add to global counters
94 // For now, we just track that a flush occurred
95 self.blocks_since_flush = 0;
96 self.flush_count += 1;
97 }
98
99 /// Get the number of times flush has been called
100 #[must_use]
101 pub fn flush_count(&self) -> usize {
102 self.flush_count
103 }
104
105 /// Get the number of blocks being tracked
106 #[must_use]
107 pub fn block_count(&self) -> usize {
108 self.local.len()
109 }
110
111 /// Check if any blocks have been hit since last flush
112 #[must_use]
113 pub fn has_pending(&self) -> bool {
114 self.blocks_since_flush > 0
115 }
116
117 /// Get the flush threshold
118 #[must_use]
119 pub fn flush_threshold(&self) -> usize {
120 self.flush_threshold
121 }
122}
123
124impl Default for ThreadLocalCounters {
125 fn default() -> Self {
126 Self::new(0)
127 }
128}