1use std::collections::HashMap;
10use std::sync::Arc;
11use std::time::{Duration, Instant};
12
13const ULTRA_HOT_SIZE: usize = 8;
15
16const HOT_MAP_CAPACITY: usize = 128;
18
19#[repr(align(64))]
23pub struct HotCache {
24 ultra_hot: [Option<HotEntry>; ULTRA_HOT_SIZE],
26 ultra_hot_next: usize,
27
28 hot_map: HashMap<u64, HotEntry>,
30
31 access_order: Vec<u64>,
33
34 ttl: Option<Duration>,
35}
36
37#[derive(Clone)]
38struct HotEntry {
39 url_hash: u64,
40 html: Arc<str>,
41 created_at: Instant,
42}
43
44impl HotCache {
45 pub fn new() -> Self {
47 Self {
48 ultra_hot: Default::default(),
49 ultra_hot_next: 0,
50 hot_map: HashMap::with_capacity(HOT_MAP_CAPACITY),
51 access_order: Vec::with_capacity(HOT_MAP_CAPACITY),
52 ttl: None,
53 }
54 }
55
56 pub fn with_ttl(ttl_secs: u64) -> Self {
58 Self {
59 ultra_hot: Default::default(),
60 ultra_hot_next: 0,
61 hot_map: HashMap::with_capacity(HOT_MAP_CAPACITY),
62 access_order: Vec::with_capacity(HOT_MAP_CAPACITY),
63 ttl: if ttl_secs > 0 {
64 Some(Duration::from_secs(ttl_secs))
65 } else {
66 None
67 },
68 }
69 }
70
71 #[inline(always)]
75 pub fn get(&mut self, url_hash: u64) -> Option<Arc<str>> {
76 for entry in self.ultra_hot.iter().flatten() {
78 if entry.url_hash == url_hash {
79 if self.is_expired(entry) {
80 return None;
81 }
82 return Some(Arc::clone(&entry.html));
83 }
84 }
85
86 if let Some(entry) = self.hot_map.get(&url_hash) {
88 if self.is_expired(entry) {
89 self.hot_map.remove(&url_hash);
90 return None;
91 }
92
93 let html = Arc::clone(&entry.html);
95 self.promote_to_ultra_hot(url_hash, Arc::clone(&html));
96 return Some(html);
97 }
98
99 None
100 }
101
102 #[inline(always)]
104 pub fn peek(&self, url_hash: u64) -> Option<Arc<str>> {
105 for entry in self.ultra_hot.iter().flatten() {
107 if entry.url_hash == url_hash {
108 if self.is_expired(entry) {
109 return None;
110 }
111 return Some(Arc::clone(&entry.html));
112 }
113 }
114
115 if let Some(entry) = self.hot_map.get(&url_hash) {
117 if self.is_expired(entry) {
118 return None;
119 }
120 return Some(Arc::clone(&entry.html));
121 }
122
123 None
124 }
125
126 #[inline(always)]
128 pub fn insert(&mut self, url_hash: u64, html: Arc<str>) {
129 let entry = HotEntry {
130 url_hash,
131 html,
132 created_at: Instant::now(),
133 };
134
135 if let Some(evicted) = self.ultra_hot[self.ultra_hot_next].take() {
138 self.insert_to_hot_map(evicted);
140 }
141
142 self.ultra_hot[self.ultra_hot_next] = Some(entry);
143 self.ultra_hot_next = (self.ultra_hot_next + 1) % ULTRA_HOT_SIZE;
144 }
145
146 fn insert_to_hot_map(&mut self, entry: HotEntry) {
148 if self.hot_map.len() >= HOT_MAP_CAPACITY {
150 if let Some(oldest_key) = self.access_order.first().copied() {
151 self.hot_map.remove(&oldest_key);
152 self.access_order.remove(0);
153 }
154 }
155
156 self.access_order.push(entry.url_hash);
157 self.hot_map.insert(entry.url_hash, entry);
158 }
159
160 fn promote_to_ultra_hot(&mut self, url_hash: u64, html: Arc<str>) {
162 self.hot_map.remove(&url_hash);
164 if let Some(pos) = self.access_order.iter().position(|&k| k == url_hash) {
165 self.access_order.remove(pos);
166 }
167
168 self.insert(url_hash, html);
170 }
171
172 #[inline(always)]
174 fn is_expired(&self, entry: &HotEntry) -> bool {
175 if let Some(ttl) = self.ttl {
176 entry.created_at.elapsed() > ttl
177 } else {
178 false
179 }
180 }
181
182 #[allow(dead_code)]
184 pub fn len(&self) -> usize {
185 let ultra_hot_count = self.ultra_hot.iter().flatten().count();
186 ultra_hot_count + self.hot_map.len()
187 }
188
189 #[allow(dead_code)]
191 pub fn is_empty(&self) -> bool {
192 self.len() == 0
193 }
194
195 #[allow(dead_code)]
197 pub fn clear(&mut self) {
198 self.ultra_hot = Default::default();
199 self.ultra_hot_next = 0;
200 self.hot_map.clear();
201 self.access_order.clear();
202 }
203}
204
205impl Default for HotCache {
206 fn default() -> Self {
207 Self::new()
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn test_basic_operations() {
217 let mut cache = HotCache::new();
218 let html: Arc<str> = "test".into();
219
220 cache.insert(123, Arc::clone(&html));
221
222 assert!(cache.get(123).is_some());
223 assert!(cache.get(456).is_none());
224 }
225
226 #[test]
227 fn test_ultra_hot_eviction() {
228 let mut cache = HotCache::new();
229
230 for i in 0..10u64 {
232 let html: Arc<str> = format!("html{}", i).into();
233 cache.insert(i, html);
234 }
235
236 assert!(cache.get(0).is_some(), "Entry 0 should be in hot_map");
239 assert!(cache.get(1).is_some(), "Entry 1 should be in hot_map");
240 assert!(cache.get(9).is_some(), "Entry 9 should be in ultra_hot");
241 }
242
243 #[test]
244 fn test_promotion() {
245 let mut cache = HotCache::new();
246
247 for i in 0..8u64 {
249 cache.insert(i, format!("html{}", i).into());
250 }
251
252 for i in 8..16u64 {
254 cache.insert(i, format!("html{}", i).into());
255 }
256
257 let _ = cache.get(0);
260
261 assert!(cache.peek(0).is_some());
263 }
264
265 #[test]
266 fn test_capacity() {
267 let mut cache = HotCache::new();
268
269 for i in 0..200u64 {
271 cache.insert(i, format!("html{}", i).into());
272 }
273
274 assert!(cache.len() <= ULTRA_HOT_SIZE + HOT_MAP_CAPACITY);
276 }
277
278 #[test]
279 fn test_hashmap_lookup_speed() {
280 let mut cache = HotCache::new();
281
282 for i in 0..100u64 {
284 cache.insert(i, format!("html{}", i).into());
285 }
286
287 assert!(cache.peek(50).is_some());
290 }
291}