Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_datime/src/cache/mod.rs

5.9 KiB, 33 runs

created by r1870400018:8323, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! Performance optimisation caching infrastructure.
2//!
3//! LRU caches for timezone calculations, string interning for format patterns,
4//! and result memoisation.
5//!
6//! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\
7//! Anthropic Claude
8
9use oxedyne_fe2o3_core::prelude::*;
10
11use std::{
12 collections::HashMap,
13 hash::Hash,
14 sync::{Arc, RwLock},
15};
16
17pub mod lru;
18pub mod string_intern;
19pub mod timezone_cache;
20
21#[derive(Debug)]
22pub struct LruCache<K, V>
23where
24 K: Hash + Eq + Clone,
25 V: Clone,
26{
27 inner: Arc<RwLock<lru::LruCacheInner<K, V>>>,
28}
29
30impl<K, V> LruCache<K, V>
31where
32 K: Hash + Eq + Clone,
33 V: Clone,
34{
35 pub fn new(capacity: usize) -> Self {
36 Self {
37 inner: Arc::new(RwLock::new(lru::LruCacheInner::new(capacity))),
38 }
39 }
40
41 pub fn get(&self, key: &K) -> Option<V> {
42 if let Ok(mut cache) = self.inner.write() {
43 cache.get(key)
44 } else {
45 None
46 }
47 }
48
49 pub fn insert(&self, key: K, value: V) {
50 if let Ok(mut cache) = self.inner.write() {
51 cache.insert(key, value);
52 }
53 }
54
55 pub fn len(&self) -> usize {
56 if let Ok(cache) = self.inner.read() {
57 cache.len()
58 } else {
59 0
60 }
61 }
62
63 pub fn is_empty(&self) -> bool {
64 self.len() == 0
65 }
66
67 pub fn clear(&self) {
68 if let Ok(mut cache) = self.inner.write() {
69 cache.clear();
70 }
71 }
72
73 /// Hits, misses and hit ratio, in that order.
74 pub fn stats(&self) -> (u64, u64, f64) {
75 if let Ok(cache) = self.inner.read() {
76 cache.stats()
77 } else {
78 (0, 0, 0.0)
79 }
80 }
81}
82
83impl<K, V> Clone for LruCache<K, V>
84where
85 K: Hash + Eq + Clone,
86 V: Clone,
87{
88 fn clone(&self) -> Self {
89 Self {
90 inner: Arc::clone(&self.inner),
91 }
92 }
93}
94
95#[derive(Debug, Clone)]
96pub struct StringInterner {
97 inner: Arc<RwLock<HashMap<String, Arc<String>>>>,
98}
99
100impl StringInterner {
101 pub fn new() -> Self {
102 Self {
103 inner: Arc::new(RwLock::new(HashMap::new())),
104 }
105 }
106
107 pub fn intern(&self, s: &str) -> Arc<String> {
108 if let Ok(cache) = self.inner.read() {
109 if let Some(interned) = cache.get(s) {
110 return Arc::clone(interned);
111 }
112 }
113
114 // Need to insert - upgrade to write lock
115 if let Ok(mut cache) = self.inner.write() {
116 // Double-check in case another thread inserted while we waited
117 if let Some(interned) = cache.get(s) {
118 return Arc::clone(interned);
119 }
120
121 let arc_string = Arc::new(s.to_string());
122 cache.insert(s.to_string(), Arc::clone(&arc_string));
123 arc_string
124 } else {
125 // Fallback if lock fails
126 Arc::new(s.to_string())
127 }
128 }
129
130 pub fn len(&self) -> usize {
131 if let Ok(cache) = self.inner.read() {
132 cache.len()
133 } else {
134 0
135 }
136 }
137
138 pub fn is_empty(&self) -> bool {
139 self.len() == 0
140 }
141
142 pub fn clear(&self) {
143 if let Ok(mut cache) = self.inner.write() {
144 cache.clear();
145 }
146 }
147}
148
149impl Default for StringInterner {
150 fn default() -> Self {
151 Self::new()
152 }
153}
154
155#[derive(Debug)]
156pub struct MemoCache<K, V>
157where
158 K: Hash + Eq + Clone,
159 V: Clone,
160{
161 cache: LruCache<K, V>,
162 hits: Arc<RwLock<u64>>,
163 misses: Arc<RwLock<u64>>,
164}
165
166impl<K, V> MemoCache<K, V>
167where
168 K: Hash + Eq + Clone,
169 V: Clone,
170{
171 pub fn new(capacity: usize) -> Self {
172 Self {
173 cache: LruCache::new(capacity),
174 hits: Arc::new(RwLock::new(0)),
175 misses: Arc::new(RwLock::new(0)),
176 }
177 }
178
179 pub fn get_or_compute<F>(&self, key: K, compute_fn: F) -> V
180 where
181 F: FnOnce() -> V,
182 {
183 if let Some(cached_value) = self.cache.get(&key) {
184 // Cache hit
185 if let Ok(mut hits) = self.hits.write() {
186 *hits += 1;
187 }
188 cached_value
189 } else {
190 // Cache miss - compute and store
191 if let Ok(mut misses) = self.misses.write() {
192 *misses += 1;
193 }
194 let computed_value = compute_fn();
195 self.cache.insert(key, computed_value.clone());
196 computed_value
197 }
198 }
199
200 /// Hits, misses and hit ratio, in that order.
201 pub fn stats(&self) -> (u64, u64, f64) {
202 let hits = if let Ok(h) = self.hits.read() { *h } else { 0 };
203 let misses = if let Ok(m) = self.misses.read() { *m } else { 0 };
204 let total = hits + misses;
205 let hit_ratio = if total > 0 { hits as f64 / total as f64 } else { 0.0 };
206 (hits, misses, hit_ratio)
207 }
208
209 pub fn clear(&self) {
210 self.cache.clear();
211 if let Ok(mut hits) = self.hits.write() {
212 *hits = 0;
213 }
214 if let Ok(mut misses) = self.misses.write() {
215 *misses = 0;
216 }
217 }
218}
219
220impl<K, V> Clone for MemoCache<K, V>
221where
222 K: Hash + Eq + Clone,
223 V: Clone,
224{
225 fn clone(&self) -> Self {
226 Self {
227 cache: self.cache.clone(),
228 hits: Arc::clone(&self.hits),
229 misses: Arc::clone(&self.misses),
230 }
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237
238 #[test]
239 fn test_lru_cache_basic_operations() {
240 let cache = LruCache::new(2);
241
242 // Test insertion and retrieval
243 cache.insert("key1".to_string(), 42);
244 cache.insert("key2".to_string(), 84);
245
246 assert_eq!(cache.get(&"key1".to_string()), Some(42));
247 assert_eq!(cache.get(&"key2".to_string()), Some(84));
248 assert_eq!(cache.len(), 2);
249
250 // Test LRU eviction
251 cache.insert("key3".to_string(), 126);
252 assert_eq!(cache.len(), 2);
253 assert_eq!(cache.get(&"key1".to_string()), None); // Should be evicted
254 assert_eq!(cache.get(&"key2".to_string()), Some(84));
255 assert_eq!(cache.get(&"key3".to_string()), Some(126));
256 }
257
258 #[test]
259 fn test_string_interner() {
260 let interner = StringInterner::new();
261
262 let str1 = interner.intern("hello");
263 let str2 = interner.intern("world");
264 let str3 = interner.intern("hello"); // Should reuse str1
265
266 assert_eq!(*str1, "hello");
267 assert_eq!(*str2, "world");
268 assert!(Arc::ptr_eq(&str1, &str3)); // Same allocation
269 assert_eq!(interner.len(), 2); // Only 2 unique strings
270 }
271
272 #[test]
273 fn test_memo_cache() {
274 let cache = MemoCache::new(10);
275
276 // First call should compute
277 let result1 = cache.get_or_compute("key1".to_string(), || 42);
278 assert_eq!(result1, 42);
279
280 // Second call should use cache
281 let result2 = cache.get_or_compute("key1".to_string(), || 99); // Different value, should not be used
282 assert_eq!(result2, 42); // Should return cached value
283
284 let (hits, misses, hit_ratio) = cache.stats();
285 assert_eq!(hits, 1);
286 assert_eq!(misses, 1);
287 assert_eq!(hit_ratio, 0.5);
288 }
289}