Skip to main content

turbo_cdn/
string_interner.rs

1//! String interning system for frequently used URLs and patterns
2//!
3//! This module provides a high-performance string interning system that reduces
4//! memory allocations by reusing common strings like CDN patterns and URLs.
5
6use dashmap::DashMap;
7use std::borrow::Cow;
8use std::sync::Arc;
9
10/// High-performance string interner using `Arc<str>` for zero-copy sharing
11#[derive(Debug, Default)]
12pub struct StringInterner {
13    /// Cache of interned strings
14    cache: DashMap<String, Arc<str>>,
15    /// Statistics for monitoring
16    stats: InternerStats,
17}
18
19/// Statistics for string interner performance monitoring
20#[derive(Debug, Default)]
21struct InternerStats {
22    /// Total number of intern requests
23    total_requests: std::sync::atomic::AtomicU64,
24    /// Number of cache hits
25    cache_hits: std::sync::atomic::AtomicU64,
26    /// Number of new strings interned
27    new_interns: std::sync::atomic::AtomicU64,
28}
29
30impl StringInterner {
31    /// Create a new string interner
32    pub fn new() -> Self {
33        Self {
34            cache: DashMap::new(),
35            stats: InternerStats::default(),
36        }
37    }
38
39    /// Intern a string, returning an `Arc<str>` for efficient sharing
40    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        // Check if already interned
46        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        // Create new interned string
52        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    /// Intern a string and return as `Cow<str>` for flexible usage
60    pub fn intern_cow(&self, s: &str) -> Cow<'static, str> {
61        let interned = self.intern(s);
62        // Convert Arc<str> to Cow<'static, str>
63        // This is safe because Arc<str> has static lifetime semantics
64        unsafe {
65            let ptr = Arc::as_ptr(&interned);
66            let static_str = &*ptr;
67            Cow::Borrowed(static_str)
68        }
69    }
70
71    /// Get cache statistics
72    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    /// Clear the cache (useful for testing or memory management)
93    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    /// Get current cache size
102    pub fn cache_size(&self) -> usize {
103        self.cache.len()
104    }
105}
106
107/// Public statistics structure
108#[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
117/// Global string interner instance for URL patterns and frequently used strings
118static GLOBAL_INTERNER: once_cell::sync::Lazy<StringInterner> =
119    once_cell::sync::Lazy::new(StringInterner::new);
120
121/// Convenience function to intern a string using the global interner
122pub fn intern_string(s: &str) -> Arc<str> {
123    GLOBAL_INTERNER.intern(s)
124}
125
126/// Convenience function to intern a string as Cow using the global interner
127pub fn intern_string_cow(s: &str) -> Cow<'static, str> {
128    GLOBAL_INTERNER.intern_cow(s)
129}
130
131/// Get global interner statistics
132pub 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        // Should be the same Arc
148        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        // Both should be borrowed variants pointing to the same data
165        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}