dashcache/
lib.rs

1/*!
2A simple, highly concurrent cache for hash-consing values
3*/
4
5use ahash::RandomState;
6use dashmap::DashMap;
7use elysees::Arc;
8use std::borrow::Borrow;
9use std::fmt::{self, Debug, Formatter};
10use std::hash::{BuildHasher, Hash};
11
12pub mod arr;
13
14/// An object which can be garbage collected
15pub trait CanCollect {
16    /// Is this handle unique or, if weak references are allowed, destroyed
17    #[inline(always)]
18    fn can_collect(&self) -> bool {
19        false
20    }
21    /// Are there two *strong* copies or less of this handle?
22    #[inline(always)]
23    fn can_collect_clone(&self) -> bool {
24        self.can_collect()
25    }
26}
27
28/// A container which can be used to cache values of type `T`
29pub trait Caches<T: ?Sized>: Hash + Eq + Clone + CanCollect {
30    /// Get a reference to the cached value of type `T`
31    fn cached(&self) -> &T;
32}
33
34/// A gloabl cache implementation
35pub trait GlobalCache {
36    /// The cache entry type
37    type Entry;
38    /**
39    Compute how many items are in a given cache.
40
41    # Example
42    ```rust
43    use dashcache::{GlobalCache, DashCache};
44    use elysees::Arc;
45    let int_cache = DashCache::<Arc<u64>>::new();
46    assert_eq!(int_cache.len(), 0);
47    int_cache.cache(10);
48    assert_eq!(int_cache.len(), 1);
49    int_cache.cache(20);
50    assert_eq!(int_cache.len(), 2);
51    // Since 10 is already in the cache, this is a no-op:
52    int_cache.cache(10);
53    assert_eq!(int_cache.len(), 2);
54    ```
55    */
56    fn len(&self) -> usize;
57    /**
58    Check if a cache is empty
59
60    # Example
61    ```rust
62    use dashcache::{GlobalCache, DashCache};
63    use elysees::Arc;
64    let int_cache = DashCache::<Arc<u64>>::new();
65    assert!(int_cache.is_empty());
66    int_cache.cache(10);
67    assert!(!int_cache.is_empty());
68    ```
69    */
70    fn is_empty(&self) -> bool;
71    /**
72    Attempt to cache a value. If already cached, return the corresponding entry.
73
74    # Example
75    ```rust
76    use dashcache::{GlobalCache, DashCache};
77    use elysees::Arc;
78    let int_cache = DashCache::<Arc<u64>>::new();
79
80    let cached_32 = int_cache.cache(32);
81    let arc_32 = Arc::new(32);
82    // These are different allocations!
83    assert!(!Arc::ptr_eq(&arc_32, &cached_32));
84
85    // We can use the cache to de-duplicate allocations
86    let dedup_32 = int_cache.cache(arc_32.clone());
87    assert!(Arc::ptr_eq(&dedup_32, &cached_32));
88
89    // Similarly, we'll get the same `Arc` if we insert the value again
90    let new_32 = int_cache.cache(32);
91    assert!(Arc::ptr_eq(&new_32, &cached_32));
92
93    // We can also insert an `Arc` from the get-go:
94    let arc_44 = Arc::new(44);
95    let cached_44 = int_cache.cache(arc_44.clone());
96    assert!(Arc::ptr_eq(&arc_44, &cached_44));
97    // New insertions are also deduplicated:
98    let dedup_44 = int_cache.cache(44);
99    assert!(Arc::ptr_eq(&arc_44, &dedup_44));
100    ```
101    */
102    fn cache<Q>(&self, value: Q) -> Self::Entry
103    where
104        Self::Entry: Borrow<Q>,
105        Q: Into<Self::Entry> + Hash + Eq;
106    /**
107    Attempt to garbage-collect this cache, returning how many elements were removed.
108    # Example
109    ```rust
110    use dashcache::{GlobalCache, DashCache};
111    use elysees::Arc;
112    let int_cache = DashCache::<Arc<u64>>::new();
113
114    // Let's stick 2 used values and 3 unused values into the cache:
115    let used_1 = int_cache.cache(77);
116    let used_2 = int_cache.cache(88);
117    int_cache.cache(99);
118    int_cache.cache(500);
119    int_cache.cache(81);
120    // We can see that at this point there are 5 things in the cache:
121    assert_eq!(int_cache.len(), 5);
122    // Now, let's garbage collect the cache, which should bring us down 3 things:
123    assert_eq!(int_cache.gc(), 3);
124    // And we have 2 things left:
125    assert_eq!(int_cache.len(), 2);
126    assert!(Arc::ptr_eq(&used_1, &int_cache.cache(77)));
127    assert!(Arc::ptr_eq(&used_2, &int_cache.cache(88)));
128    ```
129
130    # Implementation Notes
131    Note that this implements conservative GC, and in general it is perfectly well defined for this to be a no-op
132    */
133    #[inline(always)]
134    fn gc(&self) -> usize {
135        0
136    }
137    /**
138    Garbage collect a single entry if doing so would not break the hash-consing property due to uniqueness.
139    Returns the removed key on success, or Err(key) on failure
140
141    # Implementation notes
142    Note that this implements conservative GC, and in general it is perfectly well defined for this to be a no-op
143    */
144    #[inline(always)]
145    fn try_gc(&self, _key: &mut Self::Entry) -> Option<Self::Entry> {
146        None
147    }
148    /**
149    Garbage collect a single entry if doing so would not break the hash-consing property due to uniqueness.
150    Returns the removed key on success, or Err(key) on failure.
151
152    # Correctness
153    This function will cause the entry borrowed from to no longer be in the hash cache, so use with caution!
154
155    # Implementation notes
156    Note that this implements conservative GC, and in general it is perfectly well defined for this to be a no-op.
157    */
158    #[inline(always)]
159    fn try_gc_global(&self, _key: &Self::Entry) -> Option<Self::Entry> {
160        None
161    }
162}
163
164/// A global cache built around a `DashMap`
165pub struct DashCache<C, S = RandomState> {
166    cache: DashMap<C, (), S>,
167}
168
169impl<C: Hash + Eq> DashCache<C> {
170    /// Create a new, empty `DashCache`
171    #[inline]
172    pub fn new() -> DashCache<C> {
173        DashCache::default()
174    }
175}
176
177impl<C: Hash + Eq, S: BuildHasher + Clone + Default> Default for DashCache<C, S> {
178    #[inline]
179    fn default() -> DashCache<C, S> {
180        DashCache {
181            cache: DashMap::default(),
182        }
183    }
184}
185
186impl<C, S> From<DashMap<C, (), S>> for DashCache<C, S> {
187    #[inline]
188    fn from(cache: DashMap<C, (), S>) -> DashCache<C, S> {
189        DashCache { cache }
190    }
191}
192
193impl<C, S> Debug for DashCache<C, S>
194where
195    C: Debug + Hash + Eq,
196    S: BuildHasher + Clone,
197{
198    fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> {
199        fmt.debug_struct("DashCache")
200            .field("cache", &self.cache)
201            .finish()
202    }
203}
204
205impl<C, S> GlobalCache for DashCache<C, S>
206where
207    S: BuildHasher + Clone,
208    C: Hash + Eq + Clone + CanCollect,
209{
210    type Entry = C;
211    #[inline]
212    fn cache<Q>(&self, value: Q) -> C
213    where
214        C: Borrow<Q>,
215        Q: Into<C> + Hash + Eq,
216    {
217        // Read-lock first!
218        // TODO: profile and see if this actually even helps efficiency at all
219        if let Some(cached) = self.cache.get(&value) {
220            return cached.key().clone();
221        }
222        self.cache.entry(value.into()).or_default().key().clone()
223    }
224    #[inline]
225    fn gc(&self) -> usize {
226        let mut collected = 0;
227        self.cache.retain(|arc, _| {
228            if arc.can_collect() {
229                collected += 1;
230                false
231            } else {
232                true
233            }
234        });
235        collected
236    }
237    #[inline]
238    fn try_gc(&self, key: &mut Self::Entry) -> Option<Self::Entry> {
239        if key.can_collect_clone() {
240            return self.cache.remove(key).map(|(key, _)| key);
241        }
242        None
243    }
244    #[inline]
245    fn try_gc_global(&self, key: &Self::Entry) -> Option<Self::Entry> {
246        if key.can_collect_clone() {
247            return self.cache.remove(key).map(|(key, _)| key);
248        }
249        None
250    }
251    #[inline]
252    fn len(&self) -> usize {
253        self.cache.len()
254    }
255    #[inline]
256    fn is_empty(&self) -> bool {
257        self.cache.is_empty()
258    }
259}
260
261impl<T: ?Sized> CanCollect for Arc<T> {
262    /// Is this handle unique or, if weak references are allowed, destroyed
263    #[inline]
264    fn can_collect(&self) -> bool {
265        self.is_unique()
266    }
267    /// Are there two *strong* copies or less of this handle?
268    #[inline(always)]
269    fn can_collect_clone(&self) -> bool {
270        Arc::count(self, std::sync::atomic::Ordering::Acquire) <= 2
271    }
272}
273
274impl<T: Hash + Eq + ?Sized> Caches<T> for Arc<T> {
275    #[inline]
276    fn cached(&self) -> &T {
277        self
278    }
279}
280
281impl<T: ?Sized> CanCollect for std::sync::Arc<T> {
282    /// Is this handle unique or, if weak references are allowed, destroyed
283    #[inline]
284    fn can_collect(&self) -> bool {
285        std::sync::Arc::strong_count(self) == 1
286    }
287    /// Are there two *strong* copies or less of this handle?
288    #[inline(always)]
289    fn can_collect_clone(&self) -> bool {
290        std::sync::Arc::strong_count(self) <= 2
291    }
292}
293
294impl<T: Hash + Eq + ?Sized> Caches<T> for std::sync::Arc<T> {
295    #[inline]
296    fn cached(&self) -> &T {
297        self
298    }
299}
300
301impl<T: ?Sized> CanCollect for std::rc::Rc<T> {
302    /// Is this handle unique or, if weak references are allowed, destroyed
303    #[inline]
304    fn can_collect(&self) -> bool {
305        std::rc::Rc::strong_count(self) == 1
306    }
307    /// Are there two *strong* copies or less of this handle?
308    #[inline(always)]
309    fn can_collect_clone(&self) -> bool {
310        std::rc::Rc::strong_count(self) <= 2
311    }
312}
313
314impl<T: Hash + Eq + ?Sized> Caches<T> for std::rc::Rc<T> {
315    #[inline]
316    fn cached(&self) -> &T {
317        self
318    }
319}