quill_sql/utils/cache/
clock_lru.rs

1use crate::buffer::FrameId;
2use crate::error::{QuillSQLError, QuillSQLResult};
3use crate::utils::cache::Replacer;
4use std::collections::HashMap;
5
6/// Clock LRU缓存替换算法实现
7/// 使用时钟算法(Clock Algorithm)实现近似LRU
8#[derive(Debug)]
9pub struct ClockLRUReplacer {
10    // 当前可替换的frame数量
11    current_size: usize,
12    // 最大容量
13    capacity: usize,
14    // 时钟指针,指向下一个要检查的位置
15    clock_hand: usize,
16    // 存储frame信息的向量
17    frames: Vec<Option<FrameEntry>>,
18    // FrameId到frames索引的映射
19    frame_map: HashMap<FrameId, usize>,
20}
21
22/// Frame条目,存储每个frame的状态
23#[derive(Debug, Clone)]
24struct FrameEntry {
25    // frame ID
26    frame_id: FrameId,
27    // 引用位,表示最近是否被访问过
28    referenced: bool,
29    // 是否可被替换
30    is_evictable: bool,
31}
32
33impl FrameEntry {
34    fn new(frame_id: FrameId) -> Self {
35        Self {
36            frame_id,
37            referenced: false, // 初始化时引用位为false
38            is_evictable: false,
39        }
40    }
41}
42
43impl Replacer for ClockLRUReplacer {
44    /// 创建一个新的ClockLRUReplacer实例
45    fn new(capacity: usize) -> Self {
46        Self {
47            current_size: 0,
48            capacity,
49            clock_hand: 0,
50            frames: vec![None; capacity],
51            frame_map: HashMap::with_capacity(capacity),
52        }
53    }
54
55    /// 记录frame的访问
56    /// 如果frame已存在,将其引用位设为true
57    /// 如果frame不存在,添加到缓存中
58    fn record_access(&mut self, frame_id: FrameId) -> QuillSQLResult<()> {
59        if let Some(&index) = self.frame_map.get(&frame_id) {
60            // frame已存在,将引用位设为true
61            if let Some(entry) = &mut self.frames[index] {
62                entry.referenced = true;
63            }
64        } else {
65            // frame不存在,需要添加
66            if self.frame_map.len() >= self.capacity {
67                return Err(QuillSQLError::Internal(
68                    "frame size exceeds capacity".to_string(),
69                ));
70            }
71
72            // 寻找一个空位
73            let mut slot_index = None;
74            for (i, slot) in self.frames.iter().enumerate() {
75                if slot.is_none() {
76                    slot_index = Some(i);
77                    break;
78                }
79            }
80
81            let index = slot_index.unwrap_or_else(|| {
82                // 如果没有空位,选择第一个未占用的位置
83                // 这种情况实际上不应该发生,因为我们已经检查了容量
84                panic!("No available slot found, this should not happen");
85            });
86
87            // 创建新条目并添加到frames中
88            let entry = FrameEntry::new(frame_id); // 初始化引用位为false
89            self.frames[index] = Some(entry);
90            self.frame_map.insert(frame_id, index);
91        }
92
93        Ok(())
94    }
95
96    /// 驱逐一个frame
97    /// 使用时钟算法选择要替换的frame
98    fn evict(&mut self) -> Option<FrameId> {
99        // 如果没有可驱逐的frame,直接返回None
100        if self.current_size == 0 {
101            return None;
102        }
103
104        // 记录起始位置,确保我们不会无限循环
105        let start_clock_hand = self.clock_hand;
106        let mut first_evictable = None;
107        let mut first_evictable_pos = None;
108
109        // 第一轮:寻找第一个引用位为false的可驱逐frame
110        loop {
111            if let Some(entry) = &mut self.frames[self.clock_hand] {
112                if entry.is_evictable {
113                    if first_evictable.is_none() {
114                        // 记录第一个找到的可驱逐frame
115                        first_evictable = Some(entry.frame_id);
116                        first_evictable_pos = Some(self.clock_hand);
117                    }
118
119                    if !entry.referenced {
120                        // 找到引用位为false的可驱逐frame
121                        let frame_id = entry.frame_id;
122                        let current_pos = self.clock_hand;
123
124                        // 移动时钟指针
125                        self.clock_hand = (self.clock_hand + 1) % self.capacity;
126
127                        // 移除frame
128                        self.frames[current_pos] = None;
129                        self.frame_map.remove(&frame_id);
130                        self.current_size -= 1;
131
132                        return Some(frame_id);
133                    } else {
134                        // 引用位为true,设置为false
135                        entry.referenced = false;
136                    }
137                }
138            }
139
140            // 移动时钟指针
141            self.clock_hand = (self.clock_hand + 1) % self.capacity;
142
143            // 如果已经遍历了一圈,退出循环
144            if self.clock_hand == start_clock_hand {
145                break;
146            }
147        }
148
149        // 第二轮:如果所有可驱逐frame的引用位都是true,选择第一个找到的可驱逐frame
150        if let Some(frame_id) = first_evictable {
151            if let Some(pos) = first_evictable_pos {
152                // 移除frame
153                self.frames[pos] = None;
154                self.frame_map.remove(&frame_id);
155                self.current_size -= 1;
156
157                // 移动时钟指针到下一个位置
158                self.clock_hand = (pos + 1) % self.capacity;
159
160                return Some(frame_id);
161            }
162        }
163
164        None
165    }
166
167    /// 设置frame是否可被驱逐
168    fn set_evictable(&mut self, frame_id: FrameId, set_evictable: bool) -> QuillSQLResult<()> {
169        if let Some(&index) = self.frame_map.get(&frame_id) {
170            if let Some(entry) = &mut self.frames[index] {
171                // 更新可驱逐状态
172                let old_evictable = entry.is_evictable;
173                entry.is_evictable = set_evictable;
174
175                // 更新可驱逐frame计数
176                if !old_evictable && set_evictable {
177                    self.current_size += 1;
178                } else if old_evictable && !set_evictable {
179                    self.current_size -= 1;
180                }
181
182                return Ok(());
183            }
184        }
185
186        Err(QuillSQLError::Internal(format!(
187            "frame {} not found",
188            frame_id
189        )))
190    }
191
192    /// 从缓存中移除指定frame
193    fn remove(&mut self, frame_id: FrameId) {
194        if let Some(index) = self.frame_map.remove(&frame_id) {
195            if let Some(entry) = &self.frames[index] {
196                if entry.is_evictable {
197                    self.current_size -= 1;
198                }
199            }
200            self.frames[index] = None;
201        }
202    }
203
204    /// 获取当前可驱逐的frame数量
205    fn size(&self) -> usize {
206        self.current_size
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213
214    #[test]
215    fn test_clock_lru_basic() {
216        let mut replacer = ClockLRUReplacer::new(3);
217
218        // 添加三个frame
219        replacer.record_access(1).unwrap();
220        replacer.record_access(2).unwrap();
221        replacer.record_access(3).unwrap();
222
223        // 设置为可驱逐
224        replacer.set_evictable(1, true).unwrap();
225        replacer.set_evictable(2, true).unwrap();
226        replacer.set_evictable(3, true).unwrap();
227
228        assert_eq!(replacer.size(), 3);
229
230        // 第一个被驱逐的应该是frame 1,因为时钟指针从0开始
231        let evicted = replacer.evict();
232        assert_eq!(evicted, Some(1));
233        assert_eq!(replacer.size(), 2);
234    }
235
236    #[test]
237    fn test_clock_lru_reference_bit() {
238        let mut replacer = ClockLRUReplacer::new(3);
239
240        // 添加三个frame
241        replacer.record_access(1).unwrap();
242        replacer.record_access(2).unwrap();
243        replacer.record_access(3).unwrap();
244
245        // 设置为可驱逐
246        replacer.set_evictable(1, true).unwrap();
247        replacer.set_evictable(2, true).unwrap();
248        replacer.set_evictable(3, true).unwrap();
249
250        // 访问frame 1,设置引用位
251        replacer.record_access(1).unwrap();
252
253        // 现在frame 1的引用位为true,其他frame的引用位为false
254        // 时钟指针从0开始,首先检查frame 1,发现引用位为true,设为false并移动到frame 2
255        // 然后检查frame 2,引用位为false,应该淘汰frame 2
256        let evicted = replacer.evict();
257        assert_eq!(evicted, Some(2));
258
259        // 时钟指针现在应该指向frame 3的位置
260        // frame 1的引用位已被重置为false
261        // 下一个被淘汰的应该是frame 3
262        let evicted = replacer.evict();
263        assert_eq!(evicted, Some(3));
264
265        // 只剩下frame 1
266        assert_eq!(replacer.size(), 1);
267    }
268
269    #[test]
270    fn test_clock_lru_remove() {
271        let mut replacer = ClockLRUReplacer::new(3);
272
273        // 添加三个frame
274        replacer.record_access(1).unwrap();
275        replacer.record_access(2).unwrap();
276        replacer.record_access(3).unwrap();
277
278        // 设置为可驱逐
279        replacer.set_evictable(1, true).unwrap();
280        replacer.set_evictable(2, true).unwrap();
281        replacer.set_evictable(3, true).unwrap();
282
283        assert_eq!(replacer.size(), 3);
284
285        // 移除frame 2
286        replacer.remove(2);
287
288        assert_eq!(replacer.size(), 2);
289
290        // 应该驱逐frame 1
291        let evicted = replacer.evict();
292        assert_eq!(evicted, Some(1));
293
294        // 只剩下frame 3
295        assert_eq!(replacer.size(), 1);
296    }
297
298    #[test]
299    fn test_clock_lru_complex() {
300        let mut replacer = ClockLRUReplacer::new(5);
301
302        // 添加五个frame
303        for i in 1..=5 {
304            replacer.record_access(i).unwrap();
305            replacer.set_evictable(i, true).unwrap();
306        }
307
308        assert_eq!(replacer.size(), 5);
309
310        // 访问frame 2和4,设置它们的引用位
311        replacer.record_access(2).unwrap();
312        replacer.record_access(4).unwrap();
313
314        // 第一轮驱逐应该是frame 1,因为时钟指针从0开始
315        let evicted = replacer.evict();
316        assert_eq!(evicted, Some(1));
317
318        // 第二轮应该是frame 3,因为frame 2的引用位被设置
319        // frame 2的引用位会被重置为false
320        let evicted = replacer.evict();
321        assert_eq!(evicted, Some(3));
322
323        // 第三轮应该是frame 5,因为frame 4的引用位被设置
324        // frame 4的引用位会被重置为false
325        let evicted = replacer.evict();
326        assert_eq!(evicted, Some(5));
327
328        // 最后剩下frame 2和4,但引用位都已被重置为false
329        assert_eq!(replacer.size(), 2);
330
331        // 再访问一次frame 2,设置引用位
332        replacer.record_access(2).unwrap();
333
334        // 下一个驱逐应该是frame 4
335        let evicted = replacer.evict();
336        assert_eq!(evicted, Some(4));
337
338        // 只剩下frame 2
339        assert_eq!(replacer.size(), 1);
340    }
341}