Skip to main content

senax_common/cache/
fast_cache.rs

1use crossbeam::epoch::{self, Atomic, Owned, Shared};
2use std::sync::{
3    Arc,
4    atomic::{AtomicBool, AtomicU64, Ordering},
5};
6
7use super::msec::{MSec, get_cache_time};
8use crate::cache::db_cache::CacheVal;
9
10const SET_ASSOCIATIVE: u64 = 12;
11const HASH_SHIFT: usize = 24;
12const TIME_MASK: u64 = (1 << HASH_SHIFT) - 1;
13
14pub struct FastCache {
15    index: Vec<AtomicU64>,
16    data: Vec<Atomic<Data>>,
17    ttl: u64,
18    lock: AtomicBool,
19}
20
21struct Data {
22    hash: u128,
23    value: Arc<dyn CacheVal>,
24}
25
26impl Data {
27    fn is_timeout(&self, msec: MSec, ttl: u64) -> bool {
28        self.value._time().less_than_ttl(msec, ttl)
29    }
30}
31
32/// The update is slow, but the loading is more than 20 times faster than Moka's cache.
33impl FastCache {
34    pub fn new(size: u64, ttl: u64) -> FastCache {
35        let size = (size
36            / (std::mem::size_of::<AtomicU64>() + std::mem::size_of::<Atomic<Data>>()) as u64)
37            as usize;
38        let size = 1usize << (std::mem::size_of::<usize>() as u32 * 8 - size.leading_zeros() - 1);
39        let mut index = Vec::with_capacity(size);
40        let mut data = Vec::with_capacity(size);
41        for _i in 0..size {
42            index.push(AtomicU64::default());
43            data.push(Atomic::null());
44        }
45
46        FastCache {
47            index,
48            data,
49            lock: AtomicBool::new(false),
50            ttl,
51        }
52    }
53
54    /// Insert a value and return evicted data if it has a different hash value than the inserted one.
55    pub fn insert(
56        &self,
57        hash: u128,
58        value: Arc<dyn CacheVal>,
59    ) -> Option<(u128, Arc<dyn CacheVal>)> {
60        let hash_u64 = (hash as u64) ^ ((hash >> 64) as u64);
61        let _lock_guard = self.lock();
62        let (now, msec) = get_cache_time();
63        let index_mask = self.index.len() as u64 - 1;
64        let hash_idx = hash_u64 & index_mask;
65        let mut idx = 0;
66        let mut time = self.index[hash_idx as usize].load(Ordering::Acquire) & TIME_MASK;
67        let mut zero_found = false;
68        let guard = &epoch::pin();
69        for i in 0..SET_ASSOCIATIVE {
70            let pos = ((hash_idx + i) & index_mask) as usize;
71            let candidate = self.index[pos].load(Ordering::Acquire);
72            if !zero_found && candidate == 0 {
73                zero_found = true;
74                idx = i;
75                continue;
76            }
77            if (candidate & !TIME_MASK) == (hash_u64 & !TIME_MASK) {
78                let ptr = self.data[pos].load_consume(guard);
79                if let Some(data) = unsafe { ptr.as_ref() }
80                    && data.hash == hash
81                {
82                    idx = i;
83                    break;
84                }
85            }
86            if !zero_found && u24_less_than(candidate & TIME_MASK, time) {
87                time = candidate & TIME_MASK;
88                idx = i;
89            }
90        }
91        let mut hash_time = (hash_u64 & !TIME_MASK) | (now & TIME_MASK);
92        if hash_time == 0 {
93            hash_time = (hash_u64 & !TIME_MASK) | ((now - 1) & TIME_MASK);
94        }
95        let pos = ((hash_idx + idx) & index_mask) as usize;
96        let old = self.data[pos].swap(Owned::new(Data { hash, value }), Ordering::SeqCst, guard);
97        self.index[pos].store(hash_time, Ordering::Release);
98
99        if !old.is_null() {
100            let ret = unsafe { old.as_ref() }
101                .filter(|v| !v.is_timeout(msec, self.ttl) && v.hash != hash)
102                .map(|v| (v.hash, v.value.clone()));
103            unsafe { guard.defer_destroy(old) };
104            guard.flush();
105            return ret;
106        }
107
108        None
109    }
110
111    pub fn get(&self, hash: u128, now: u64, msec: MSec) -> Option<Arc<dyn CacheVal>> {
112        let index_mask = self.index.len() as u64 - 1;
113        let hash_u64 = (hash as u64) ^ ((hash >> 64) as u64);
114        let hash_idx = hash_u64 & index_mask;
115        let guard = &epoch::pin();
116        for i in 0..SET_ASSOCIATIVE {
117            let pos = ((hash_idx + i) & index_mask) as usize;
118            let candidate = self.index[pos].load(Ordering::Acquire);
119            if candidate != 0 && (candidate & !TIME_MASK) == (hash_u64 & !TIME_MASK) {
120                let ptr = self.data[pos].load_consume(guard);
121                if let Some(data) = unsafe { ptr.as_ref() }
122                    && data.hash == hash
123                {
124                    if data.is_timeout(msec, self.ttl) {
125                        let _lock_guard = self.lock();
126                        let old = self.data[pos].swap(Shared::null(), Ordering::SeqCst, guard);
127                        if !old.is_null() {
128                            unsafe { guard.defer_destroy(old) };
129                            guard.flush();
130                        }
131                        self.index[pos].store(0, Ordering::Release);
132                        return None;
133                    }
134                    let hash_time = (hash_u64 & !TIME_MASK) | (now & TIME_MASK);
135                    if candidate != hash_time && hash_time != 0 {
136                        let _ = self.index[pos].compare_exchange_weak(
137                            candidate,
138                            hash_time,
139                            Ordering::Relaxed,
140                            Ordering::Relaxed,
141                        );
142                    }
143                    return Some(Arc::clone(&data.value));
144                }
145            }
146        }
147        None
148    }
149
150    pub fn invalidate(&self, hash: u128) {
151        let _lock_guard = self.lock();
152        let index_mask = self.index.len() as u64 - 1;
153        let hash_u64 = (hash as u64) ^ ((hash >> 64) as u64);
154        let hash_idx = hash_u64 & index_mask;
155        for i in 0..SET_ASSOCIATIVE {
156            let pos = ((hash_idx + i) & index_mask) as usize;
157            let candidate = self.index[pos].load(Ordering::Acquire);
158            if candidate != 0 && (candidate & !TIME_MASK) == (hash_u64 & !TIME_MASK) {
159                let guard = &epoch::pin();
160                let ptr = self.data[pos].load_consume(guard);
161                if let Some(data) = unsafe { ptr.as_ref() }
162                    && data.hash == hash
163                {
164                    let old = self.data[pos].swap(Shared::null(), Ordering::SeqCst, guard);
165                    if !old.is_null() {
166                        unsafe { guard.defer_destroy(old) };
167                        guard.flush();
168                    }
169                    self.index[pos].store(0, Ordering::Release);
170                }
171                return;
172            }
173        }
174    }
175
176    pub fn invalidate_all_of(&self, type_id: u64) {
177        let _lock_guard = self.lock();
178        let guard = &epoch::pin();
179        for i in 0..self.data.len() {
180            let ptr = self.data[i].load_consume(guard);
181            if let Some(data) = unsafe { ptr.as_ref() }
182                && data.value._type_id() == type_id
183            {
184                let old = self.data[i].swap(Shared::null(), Ordering::SeqCst, guard);
185                if !old.is_null() {
186                    unsafe { guard.defer_destroy(old) };
187                }
188                self.index[i].store(0, Ordering::Release);
189            }
190        }
191        guard.flush();
192    }
193
194    pub fn invalidate_all(&self) {
195        let _lock_guard = self.lock();
196        let guard = &epoch::pin();
197        for i in 0..self.data.len() {
198            let ptr = self.data[i].load_consume(guard);
199            if !ptr.is_null() {
200                let old = self.data[i].swap(Shared::null(), Ordering::SeqCst, guard);
201                if !old.is_null() {
202                    unsafe { guard.defer_destroy(old) };
203                }
204                self.index[i].store(0, Ordering::Release);
205            }
206        }
207        guard.flush();
208    }
209
210    fn lock(&self) -> LockGuard<'_> {
211        while self
212            .lock
213            .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
214            .is_err()
215        {
216            std::thread::yield_now();
217        }
218        LockGuard { lock: &self.lock }
219    }
220}
221
222struct LockGuard<'a> {
223    lock: &'a AtomicBool,
224}
225
226impl<'a> Drop for LockGuard<'a> {
227    fn drop(&mut self) {
228        self.lock.store(false, Ordering::Release);
229    }
230}
231
232impl Drop for FastCache {
233    fn drop(&mut self) {
234        self.invalidate_all();
235    }
236}
237
238fn u24_less_than(lhs: u64, rhs: u64) -> bool {
239    let lhs = lhs & 0xFFFFFF;
240    let rhs = rhs & 0xFFFFFF;
241    let lhs = lhs | ((lhs & 0x800000) * 0x1FFFFFFFFFE);
242    let rhs = rhs | ((rhs & 0x800000) * 0x1FFFFFFFFFE);
243    lhs.wrapping_sub(rhs) > u64::MAX / 2
244}
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249
250    #[derive(Clone, Debug)]
251    struct A(u32);
252    impl CacheVal for A {
253        fn _size(&self) -> u32 {
254            10
255        }
256        fn _type_id(&self) -> u64 {
257            Self::__type_id()
258        }
259        fn __type_id() -> u64 {
260            1
261        }
262        fn _shard_id(&self) -> crate::ShardId {
263            1
264        }
265        fn _time(&self) -> MSec {
266            MSec::now()
267        }
268        fn _estimate() -> usize {
269            10
270        }
271        fn _encode(&self) -> anyhow::Result<Vec<u8>> {
272            Ok(Vec::new())
273        }
274        fn _decode(_v: &[u8]) -> anyhow::Result<Self> {
275            Ok(Self(1))
276        }
277    }
278
279    #[test]
280    fn test_u40_less_than() {
281        assert!(u24_less_than(1, 2));
282        assert!(!u24_less_than(1, 1));
283        assert!(!u24_less_than(2, 1));
284        assert!(u24_less_than(0xffffff, 0));
285        assert!(u24_less_than(0xfffffe, 0xffffff));
286    }
287    #[test]
288    fn test() {
289        let cache = Arc::new(FastCache::new(16, 1000));
290        let cache2 = cache.clone();
291        let (now, msec) = get_cache_time();
292        std::thread::spawn(move || {
293            cache.insert(1, Arc::new(A(1)));
294            cache.insert(1, Arc::new(A(1)));
295            let _result = cache.get(1, now, msec);
296            // println!("{:?}", result);
297        })
298        .join()
299        .unwrap();
300        std::thread::spawn(move || {
301            cache2.insert(2, Arc::new(A(2)));
302            let _result = cache2.get(2, now, msec);
303            // println!("{:?}", result);
304        })
305        .join()
306        .unwrap();
307    }
308}