use std::collections::HashMap;
use std::sync::Arc;
use std::time::{Duration, Instant};
const ULTRA_HOT_SIZE: usize = 8;
const HOT_MAP_CAPACITY: usize = 128;
#[repr(align(64))]
pub struct HotCache {
ultra_hot: [Option<HotEntry>; ULTRA_HOT_SIZE],
ultra_hot_next: usize,
hot_map: HashMap<u64, HotEntry>,
access_order: Vec<u64>,
ttl: Option<Duration>,
}
#[derive(Clone)]
struct HotEntry {
url_hash: u64,
html: Arc<str>,
created_at: Instant,
}
impl HotCache {
pub fn new() -> Self {
Self {
ultra_hot: Default::default(),
ultra_hot_next: 0,
hot_map: HashMap::with_capacity(HOT_MAP_CAPACITY),
access_order: Vec::with_capacity(HOT_MAP_CAPACITY),
ttl: None,
}
}
pub fn with_ttl(ttl_secs: u64) -> Self {
Self {
ultra_hot: Default::default(),
ultra_hot_next: 0,
hot_map: HashMap::with_capacity(HOT_MAP_CAPACITY),
access_order: Vec::with_capacity(HOT_MAP_CAPACITY),
ttl: if ttl_secs > 0 {
Some(Duration::from_secs(ttl_secs))
} else {
None
},
}
}
#[inline(always)]
pub fn get(&mut self, url_hash: u64) -> Option<Arc<str>> {
for entry in self.ultra_hot.iter().flatten() {
if entry.url_hash == url_hash {
if self.is_expired(entry) {
return None;
}
return Some(Arc::clone(&entry.html));
}
}
if let Some(entry) = self.hot_map.get(&url_hash) {
if self.is_expired(entry) {
self.hot_map.remove(&url_hash);
return None;
}
let html = Arc::clone(&entry.html);
self.promote_to_ultra_hot(url_hash, Arc::clone(&html));
return Some(html);
}
None
}
#[inline(always)]
pub fn peek(&self, url_hash: u64) -> Option<Arc<str>> {
for entry in self.ultra_hot.iter().flatten() {
if entry.url_hash == url_hash {
if self.is_expired(entry) {
return None;
}
return Some(Arc::clone(&entry.html));
}
}
if let Some(entry) = self.hot_map.get(&url_hash) {
if self.is_expired(entry) {
return None;
}
return Some(Arc::clone(&entry.html));
}
None
}
#[inline(always)]
pub fn insert(&mut self, url_hash: u64, html: Arc<str>) {
let entry = HotEntry {
url_hash,
html,
created_at: Instant::now(),
};
if let Some(evicted) = self.ultra_hot[self.ultra_hot_next].take() {
self.insert_to_hot_map(evicted);
}
self.ultra_hot[self.ultra_hot_next] = Some(entry);
self.ultra_hot_next = (self.ultra_hot_next + 1) % ULTRA_HOT_SIZE;
}
fn insert_to_hot_map(&mut self, entry: HotEntry) {
if self.hot_map.len() >= HOT_MAP_CAPACITY {
if let Some(oldest_key) = self.access_order.first().copied() {
self.hot_map.remove(&oldest_key);
self.access_order.remove(0);
}
}
self.access_order.push(entry.url_hash);
self.hot_map.insert(entry.url_hash, entry);
}
fn promote_to_ultra_hot(&mut self, url_hash: u64, html: Arc<str>) {
self.hot_map.remove(&url_hash);
if let Some(pos) = self.access_order.iter().position(|&k| k == url_hash) {
self.access_order.remove(pos);
}
self.insert(url_hash, html);
}
#[inline(always)]
fn is_expired(&self, entry: &HotEntry) -> bool {
if let Some(ttl) = self.ttl {
entry.created_at.elapsed() > ttl
} else {
false
}
}
#[allow(dead_code)]
pub fn len(&self) -> usize {
let ultra_hot_count = self.ultra_hot.iter().flatten().count();
ultra_hot_count + self.hot_map.len()
}
#[allow(dead_code)]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[allow(dead_code)]
pub fn clear(&mut self) {
self.ultra_hot = Default::default();
self.ultra_hot_next = 0;
self.hot_map.clear();
self.access_order.clear();
}
}
impl Default for HotCache {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_operations() {
let mut cache = HotCache::new();
let html: Arc<str> = "test".into();
cache.insert(123, Arc::clone(&html));
assert!(cache.get(123).is_some());
assert!(cache.get(456).is_none());
}
#[test]
fn test_ultra_hot_eviction() {
let mut cache = HotCache::new();
for i in 0..10u64 {
let html: Arc<str> = format!("html{}", i).into();
cache.insert(i, html);
}
assert!(cache.get(0).is_some(), "Entry 0 should be in hot_map");
assert!(cache.get(1).is_some(), "Entry 1 should be in hot_map");
assert!(cache.get(9).is_some(), "Entry 9 should be in ultra_hot");
}
#[test]
fn test_promotion() {
let mut cache = HotCache::new();
for i in 0..8u64 {
cache.insert(i, format!("html{}", i).into());
}
for i in 8..16u64 {
cache.insert(i, format!("html{}", i).into());
}
let _ = cache.get(0);
assert!(cache.peek(0).is_some());
}
#[test]
fn test_capacity() {
let mut cache = HotCache::new();
for i in 0..200u64 {
cache.insert(i, format!("html{}", i).into());
}
assert!(cache.len() <= ULTRA_HOT_SIZE + HOT_MAP_CAPACITY);
}
#[test]
fn test_hashmap_lookup_speed() {
let mut cache = HotCache::new();
for i in 0..100u64 {
cache.insert(i, format!("html{}", i).into());
}
assert!(cache.peek(50).is_some());
}
}