dualcache_ff/config.rs
1#[derive(Debug, Clone, Copy)]
2pub struct Config {
3 pub capacity: usize,
4 pub t1_slots: usize,
5 pub t2_slots: usize,
6 /// TTL duration in epoch ticks (one tick ≈ 100 ms in std mode).
7 pub duration: u32,
8 pub threads: usize,
9 /// Daemon poll interval in **microseconds** (1 000–10 000 µs = 1–10 ms).
10 /// Controls the latency vs. idle-CPU trade-off.
11 /// Lower = faster hit-signal delivery, higher CPU when idle.
12 /// Higher = more efficient idle, but hotter TLS buffers may stall longer.
13 pub poll_us: u64,
14 /// TLS flush threshold in **daemon ticks**.
15 /// A Worker forces a TLS buffer flush when it detects that the Daemon
16 /// tick counter has advanced by at least this many ticks since the last
17 /// flush, regardless of buffer fill level.
18 ///
19 /// Rule of thumb: `flush_tick_threshold ≈ 1ms / poll_us`.
20 /// For poll_us = 1 000 µs (1 ms): threshold = 1.
21 /// For poll_us = 5 000 µs (5 ms): threshold = 1 (flush every 5 ms).
22 pub flush_tick_threshold: u64,
23}
24
25impl Config {
26 /// Budget-based constructor: specify RAM and TTL, the engine picks sizes.
27 pub fn with_memory_budget(ram_mb: usize, duration: u32) -> Self {
28 // Assume total overhead per item is ~128 bytes
29 let raw_capacity = (ram_mb * 1024 * 1024) / 128;
30 let capacity = raw_capacity.next_power_of_two().max(256);
31
32 Self {
33 capacity,
34 // T1 fits in L1 cache: max 2048 × 8-byte pointers = 16 KB
35 t1_slots: 2048,
36 // T2 intercepts warm data: 20% of capacity (80/20 rule)
37 t2_slots: (capacity / 5).next_power_of_two().max(4096),
38 duration,
39 #[cfg(feature = "std")]
40 threads: std::thread::available_parallelism()
41 .map(|p| p.get())
42 .unwrap_or(16),
43 #[cfg(not(feature = "std"))]
44 threads: 8,
45 poll_us: 1_000,
46 flush_tick_threshold: 1,
47 }
48 }
49
50 /// Expert constructor with explicit physical-law assertions.
51 pub fn new_expert(
52 capacity: usize,
53 t1_slots: usize,
54 t2_slots: usize,
55 duration: u32,
56 threads: usize,
57 ) -> Self {
58 // Physical Law 1: Bitmask routing requires powers of two
59 assert!(capacity.is_power_of_two(), "Capacity MUST be a power of two");
60 assert!(t1_slots.is_power_of_two(), "T1 slots MUST be a power of two");
61 assert!(t2_slots.is_power_of_two(), "T2 slots MUST be a power of two");
62
63 // Physical Law 2: T1 absolutely cannot exceed L1 cache
64 assert!(
65 t1_slots <= 4096,
66 "T1 size exceeds L1 Cache physical limits! Max slots: 4096"
67 );
68
69 Self {
70 capacity,
71 t1_slots,
72 t2_slots,
73 duration,
74 threads,
75 poll_us: 1_000,
76 flush_tick_threshold: 1,
77 }
78 }
79
80 /// Builder: set Daemon poll interval (1 000–10 000 µs).
81 pub fn with_poll_us(mut self, poll_us: u64) -> Self {
82 let clamped = if poll_us < 1_000 {
83 1_000
84 } else if poll_us > 10_000 {
85 10_000
86 } else {
87 poll_us
88 };
89 self.poll_us = clamped;
90 self
91 }
92
93 /// Builder: set TLS flush threshold in daemon ticks.
94 pub fn with_flush_tick_threshold(mut self, ticks: u64) -> Self {
95 self.flush_tick_threshold = if ticks < 1 { 1 } else { ticks };
96 self
97 }
98}