turbo_cdn/
string_interner.rs1use dashmap::DashMap;
7use std::borrow::Cow;
8use std::sync::Arc;
9
10#[derive(Debug, Default)]
12pub struct StringInterner {
13 cache: DashMap<String, Arc<str>>,
15 stats: InternerStats,
17}
18
19#[derive(Debug, Default)]
21struct InternerStats {
22 total_requests: std::sync::atomic::AtomicU64,
24 cache_hits: std::sync::atomic::AtomicU64,
26 new_interns: std::sync::atomic::AtomicU64,
28}
29
30impl StringInterner {
31 pub fn new() -> Self {
33 Self {
34 cache: DashMap::new(),
35 stats: InternerStats::default(),
36 }
37 }
38
39 pub fn intern(&self, s: &str) -> Arc<str> {
41 use std::sync::atomic::Ordering;
42
43 self.stats.total_requests.fetch_add(1, Ordering::Relaxed);
44
45 if let Some(interned) = self.cache.get(s) {
47 self.stats.cache_hits.fetch_add(1, Ordering::Relaxed);
48 return interned.clone();
49 }
50
51 let arc_str: Arc<str> = Arc::from(s);
53 self.cache.insert(s.to_string(), arc_str.clone());
54 self.stats.new_interns.fetch_add(1, Ordering::Relaxed);
55
56 arc_str
57 }
58
59 pub fn intern_cow(&self, s: &str) -> Cow<'static, str> {
61 let interned = self.intern(s);
62 unsafe {
65 let ptr = Arc::as_ptr(&interned);
66 let static_str = &*ptr;
67 Cow::Borrowed(static_str)
68 }
69 }
70
71 pub fn stats(&self) -> InternerStatistics {
73 use std::sync::atomic::Ordering;
74
75 InternerStatistics {
76 total_requests: self.stats.total_requests.load(Ordering::Relaxed),
77 cache_hits: self.stats.cache_hits.load(Ordering::Relaxed),
78 new_interns: self.stats.new_interns.load(Ordering::Relaxed),
79 cache_size: self.cache.len(),
80 hit_rate: {
81 let total = self.stats.total_requests.load(Ordering::Relaxed);
82 let hits = self.stats.cache_hits.load(Ordering::Relaxed);
83 if total > 0 {
84 (hits as f64 / total as f64) * 100.0
85 } else {
86 0.0
87 }
88 },
89 }
90 }
91
92 pub fn clear(&self) {
94 self.cache.clear();
95 use std::sync::atomic::Ordering;
96 self.stats.total_requests.store(0, Ordering::Relaxed);
97 self.stats.cache_hits.store(0, Ordering::Relaxed);
98 self.stats.new_interns.store(0, Ordering::Relaxed);
99 }
100
101 pub fn cache_size(&self) -> usize {
103 self.cache.len()
104 }
105}
106
107#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
109pub struct InternerStatistics {
110 pub total_requests: u64,
111 pub cache_hits: u64,
112 pub new_interns: u64,
113 pub cache_size: usize,
114 pub hit_rate: f64,
115}
116
117static GLOBAL_INTERNER: once_cell::sync::Lazy<StringInterner> =
119 once_cell::sync::Lazy::new(StringInterner::new);
120
121pub fn intern_string(s: &str) -> Arc<str> {
123 GLOBAL_INTERNER.intern(s)
124}
125
126pub fn intern_string_cow(s: &str) -> Cow<'static, str> {
128 GLOBAL_INTERNER.intern_cow(s)
129}
130
131pub fn global_interner_stats() -> InternerStatistics {
133 GLOBAL_INTERNER.stats()
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn test_string_interning() {
142 let interner = StringInterner::new();
143
144 let s1 = interner.intern("test");
145 let s2 = interner.intern("test");
146
147 assert!(Arc::ptr_eq(&s1, &s2));
149
150 let stats = interner.stats();
151 assert_eq!(stats.total_requests, 2);
152 assert_eq!(stats.cache_hits, 1);
153 assert_eq!(stats.new_interns, 1);
154 assert_eq!(stats.hit_rate, 50.0);
155 }
156
157 #[test]
158 fn test_cow_interning() {
159 let interner = StringInterner::new();
160
161 let cow1 = interner.intern_cow("test");
162 let cow2 = interner.intern_cow("test");
163
164 match (&cow1, &cow2) {
166 (Cow::Borrowed(s1), Cow::Borrowed(s2)) => {
167 assert_eq!(s1.as_ptr(), s2.as_ptr());
168 }
169 _ => panic!("Expected borrowed variants"),
170 }
171 }
172
173 #[test]
174 fn test_global_interner() {
175 let s1 = intern_string("global_test");
176 let s2 = intern_string("global_test");
177
178 assert!(Arc::ptr_eq(&s1, &s2));
179
180 let stats = global_interner_stats();
181 assert!(stats.total_requests >= 2);
182 }
183
184 #[test]
185 fn test_interner_clear() {
186 let interner = StringInterner::new();
187
188 interner.intern("test1");
189 interner.intern("test2");
190
191 assert_eq!(interner.cache_size(), 2);
192
193 interner.clear();
194
195 assert_eq!(interner.cache_size(), 0);
196 let stats = interner.stats();
197 assert_eq!(stats.total_requests, 0);
198 }
199}