offheap 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,355 @@
1
+ use std::collections::HashMap;
2
+ use super::{CacheImpl, CacheEntry, CacheStats};
3
+
4
+ #[derive(Copy, Clone, PartialEq, Debug)]
5
+ enum ArcList {
6
+ T1,
7
+ B1,
8
+ T2,
9
+ B2,
10
+ }
11
+
12
+ struct ArcNode {
13
+ key: String,
14
+ value: Option<CacheEntry>, // Only T1 and T2 store values. B1 and B2 are ghost entries.
15
+ list: ArcList,
16
+ prev: Option<usize>,
17
+ next: Option<usize>,
18
+ }
19
+
20
+ struct ArcListMetadata {
21
+ head: Option<usize>,
22
+ tail: Option<usize>,
23
+ len: usize,
24
+ }
25
+
26
+ impl ArcListMetadata {
27
+ fn new() -> Self {
28
+ Self {
29
+ head: None,
30
+ tail: None,
31
+ len: 0,
32
+ }
33
+ }
34
+ }
35
+
36
+ pub struct ArcCache {
37
+ capacity: usize,
38
+ map: HashMap<String, usize>,
39
+ nodes: Vec<ArcNode>,
40
+ free_nodes: Vec<usize>,
41
+
42
+ t1: ArcListMetadata,
43
+ b1: ArcListMetadata,
44
+ t2: ArcListMetadata,
45
+ b2: ArcListMetadata,
46
+
47
+ p: usize, // Target size for T1
48
+ hits: u64,
49
+ misses: u64,
50
+ }
51
+
52
+ impl ArcCache {
53
+ pub fn new(capacity: usize) -> Self {
54
+ Self {
55
+ capacity,
56
+ map: HashMap::with_capacity(capacity * 2), // Map contains history as well
57
+ nodes: Vec::with_capacity(capacity * 2),
58
+ free_nodes: Vec::new(),
59
+ t1: ArcListMetadata::new(),
60
+ b1: ArcListMetadata::new(),
61
+ t2: ArcListMetadata::new(),
62
+ b2: ArcListMetadata::new(),
63
+ p: 0,
64
+ hits: 0,
65
+ misses: 0,
66
+ }
67
+ }
68
+
69
+ fn detach(&mut self, idx: usize) {
70
+ let prev = self.nodes[idx].prev;
71
+ let next = self.nodes[idx].next;
72
+ let list = self.nodes[idx].list;
73
+
74
+ let meta = match list {
75
+ ArcList::T1 => &mut self.t1,
76
+ ArcList::B1 => &mut self.b1,
77
+ ArcList::T2 => &mut self.t2,
78
+ ArcList::B2 => &mut self.b2,
79
+ };
80
+
81
+ if let Some(p) = prev {
82
+ self.nodes[p].next = next;
83
+ } else {
84
+ meta.head = next;
85
+ }
86
+
87
+ if let Some(n) = next {
88
+ self.nodes[n].prev = prev;
89
+ } else {
90
+ meta.tail = prev;
91
+ }
92
+
93
+ meta.len -= 1;
94
+ self.nodes[idx].prev = None;
95
+ self.nodes[idx].next = None;
96
+ }
97
+
98
+ fn attach_to_head(&mut self, idx: usize, list: ArcList) {
99
+ self.nodes[idx].list = list;
100
+ let meta = match list {
101
+ ArcList::T1 => &mut self.t1,
102
+ ArcList::B1 => &mut self.b1,
103
+ ArcList::T2 => &mut self.t2,
104
+ ArcList::B2 => &mut self.b2,
105
+ };
106
+
107
+ self.nodes[idx].prev = None;
108
+ self.nodes[idx].next = meta.head;
109
+
110
+ if let Some(h) = meta.head {
111
+ self.nodes[h].prev = Some(idx);
112
+ } else {
113
+ meta.tail = Some(idx);
114
+ }
115
+
116
+ meta.head = Some(idx);
117
+ meta.len += 1;
118
+ }
119
+
120
+ fn remove_completely(&mut self, idx: usize) -> String {
121
+ self.detach(idx);
122
+ self.free_nodes.push(idx);
123
+ let key = std::mem::take(&mut self.nodes[idx].key);
124
+ self.nodes[idx].value = None;
125
+ key
126
+ }
127
+
128
+ fn replace(&mut self, key_in_b2: bool) {
129
+ if self.t1.len > 0 && (self.t1.len > self.p || (key_in_b2 && self.t1.len == self.p)) {
130
+ if let Some(t1_tail) = self.t1.tail {
131
+ self.detach(t1_tail);
132
+ self.nodes[t1_tail].value = None; // Evict value from memory
133
+ self.attach_to_head(t1_tail, ArcList::B1);
134
+ }
135
+ } else {
136
+ if let Some(t2_tail) = self.t2.tail {
137
+ self.detach(t2_tail);
138
+ self.nodes[t2_tail].value = None; // Evict value from memory
139
+ self.attach_to_head(t2_tail, ArcList::B2);
140
+ }
141
+ }
142
+ }
143
+ }
144
+
145
+ impl CacheImpl for ArcCache {
146
+ fn get(&mut self, key: &str) -> Option<Vec<u8>> {
147
+ if let Some(&idx) = self.map.get(key) {
148
+ let list = self.nodes[idx].list;
149
+ match list {
150
+ ArcList::T1 | ArcList::T2 => {
151
+ let expired = if let Some(ref entry) = self.nodes[idx].value {
152
+ entry.is_expired()
153
+ } else {
154
+ true
155
+ };
156
+
157
+ if expired {
158
+ self.remove_completely(idx);
159
+ self.map.remove(key);
160
+ self.misses += 1;
161
+ None
162
+ } else {
163
+ self.detach(idx);
164
+ self.attach_to_head(idx, ArcList::T2);
165
+ self.hits += 1;
166
+ self.nodes[idx].value.as_ref().map(|e| e.value.clone())
167
+ }
168
+ }
169
+ ArcList::B1 | ArcList::B2 => {
170
+ // History hit. We can't return value (evicted), so counts as miss for user,
171
+ // but we trigger adaptation.
172
+ self.misses += 1;
173
+ None
174
+ }
175
+ }
176
+ } else {
177
+ self.misses += 1;
178
+ None
179
+ }
180
+ }
181
+
182
+ fn set(&mut self, key: &str, value: Vec<u8>, ttl_ms: Option<u64>) -> Option<Vec<u8>> {
183
+ if self.capacity == 0 {
184
+ return None;
185
+ }
186
+
187
+ if let Some(&idx) = self.map.get(key) {
188
+ let list = self.nodes[idx].list;
189
+ match list {
190
+ ArcList::T1 | ArcList::T2 => {
191
+ // Cache hit. Update value.
192
+ let old_value = self.nodes[idx].value.replace(CacheEntry::new(value, ttl_ms)).map(|e| e.value);
193
+ self.detach(idx);
194
+ self.attach_to_head(idx, ArcList::T2);
195
+ old_value
196
+ }
197
+ ArcList::B1 => {
198
+ // Hit in B1: we need more recency
199
+ let b1_len = if self.b1.len == 0 { 1 } else { self.b1.len };
200
+ let delta = std::cmp::max(1, self.b2.len / b1_len);
201
+ self.p = std::cmp::min(self.p + delta, self.capacity);
202
+
203
+ self.replace(false);
204
+
205
+ self.detach(idx);
206
+ self.nodes[idx].value = Some(CacheEntry::new(value, ttl_ms));
207
+ self.attach_to_head(idx, ArcList::T2);
208
+ None
209
+ }
210
+ ArcList::B2 => {
211
+ // Hit in B2: we need more frequency
212
+ let b2_len = if self.b2.len == 0 { 1 } else { self.b2.len };
213
+ let delta = std::cmp::max(1, self.b1.len / b2_len);
214
+ self.p = self.p.saturating_sub(delta);
215
+
216
+ self.replace(true);
217
+
218
+ self.detach(idx);
219
+ self.nodes[idx].value = Some(CacheEntry::new(value, ttl_ms));
220
+ self.attach_to_head(idx, ArcList::T2);
221
+ None
222
+ }
223
+ }
224
+ } else {
225
+ // Cache Miss (not in cache, nor in history)
226
+ let l1_len = self.t1.len + self.b1.len;
227
+ let l2_len = self.t2.len + self.b2.len;
228
+
229
+ if l1_len == self.capacity {
230
+ if self.t1.len < self.capacity {
231
+ if let Some(b1_tail) = self.b1.tail {
232
+ let evicted_key = self.remove_completely(b1_tail);
233
+ self.map.remove(&evicted_key);
234
+ }
235
+ self.replace(false);
236
+ } else {
237
+ if let Some(t1_tail) = self.t1.tail {
238
+ let evicted_key = self.remove_completely(t1_tail);
239
+ self.map.remove(&evicted_key);
240
+ }
241
+ }
242
+ } else if l1_len < self.capacity && l1_len + l2_len >= self.capacity {
243
+ if l1_len + l2_len == 2 * self.capacity {
244
+ if let Some(b2_tail) = self.b2.tail {
245
+ let evicted_key = self.remove_completely(b2_tail);
246
+ self.map.remove(&evicted_key);
247
+ }
248
+ }
249
+ self.replace(false);
250
+ }
251
+
252
+ // Create new node
253
+ let idx = if let Some(free_idx) = self.free_nodes.pop() {
254
+ self.nodes[free_idx].key = key.to_string();
255
+ self.nodes[free_idx].value = Some(CacheEntry::new(value, ttl_ms));
256
+ free_idx
257
+ } else {
258
+ let free_idx = self.nodes.len();
259
+ self.nodes.push(ArcNode {
260
+ key: key.to_string(),
261
+ value: Some(CacheEntry::new(value, ttl_ms)),
262
+ list: ArcList::T1,
263
+ prev: None,
264
+ next: None,
265
+ });
266
+ free_idx
267
+ };
268
+
269
+ self.attach_to_head(idx, ArcList::T1);
270
+ self.map.insert(key.to_string(), idx);
271
+ None
272
+ }
273
+ }
274
+
275
+ fn delete(&mut self, key: &str) -> bool {
276
+ if let Some(&idx) = self.map.get(key) {
277
+ self.remove_completely(idx);
278
+ self.map.remove(key);
279
+ true
280
+ } else {
281
+ false
282
+ }
283
+ }
284
+
285
+ fn clear(&mut self) {
286
+ self.map.clear();
287
+ self.nodes.clear();
288
+ self.free_nodes.clear();
289
+ self.t1 = ArcListMetadata::new();
290
+ self.b1 = ArcListMetadata::new();
291
+ self.t2 = ArcListMetadata::new();
292
+ self.b2 = ArcListMetadata::new();
293
+ self.p = 0;
294
+ self.hits = 0;
295
+ self.misses = 0;
296
+ }
297
+
298
+ fn stats(&self) -> CacheStats {
299
+ CacheStats {
300
+ hits: self.hits,
301
+ misses: self.misses,
302
+ capacity: self.capacity,
303
+ size: self.t1.len + self.t2.len,
304
+ }
305
+ }
306
+
307
+ fn keys(&self) -> Vec<String> {
308
+ let mut result = Vec::new();
309
+ // Return active keys in T1
310
+ let mut curr = self.t1.head;
311
+ while let Some(idx) = curr {
312
+ let node = &self.nodes[idx];
313
+ let expired = node.value.as_ref().map_or(true, |e| e.is_expired());
314
+ if !expired {
315
+ result.push(node.key.clone());
316
+ }
317
+ curr = node.next;
318
+ }
319
+ // Return active keys in T2
320
+ let mut curr = self.t2.head;
321
+ while let Some(idx) = curr {
322
+ let node = &self.nodes[idx];
323
+ let expired = node.value.as_ref().map_or(true, |e| e.is_expired());
324
+ if !expired {
325
+ result.push(node.key.clone());
326
+ }
327
+ curr = node.next;
328
+ }
329
+ result
330
+ }
331
+ }
332
+
333
+ #[cfg(test)]
334
+ mod tests {
335
+ use super::*;
336
+
337
+ #[test]
338
+ fn test_arc_basic() {
339
+ let mut cache = ArcCache::new(3);
340
+ cache.set("a", vec![1], None);
341
+ cache.set("b", vec![2], None);
342
+ cache.set("c", vec![3], None);
343
+
344
+ // Hits to move to T2
345
+ assert_eq!(cache.get("a"), Some(vec![1]));
346
+ assert_eq!(cache.get("b"), Some(vec![2]));
347
+
348
+ // Set new key, should evict c (which was in T1 and never hit)
349
+ cache.set("d", vec![4], None);
350
+ assert_eq!(cache.get("c"), None);
351
+ assert_eq!(cache.get("a"), Some(vec![1]));
352
+ assert_eq!(cache.get("b"), Some(vec![2]));
353
+ assert_eq!(cache.get("d"), Some(vec![4]));
354
+ }
355
+ }
@@ -0,0 +1,212 @@
1
+ use std::collections::HashMap;
2
+ use super::{CacheImpl, CacheEntry, CacheStats};
3
+
4
+ struct LruNode {
5
+ key: String,
6
+ entry: CacheEntry,
7
+ prev: Option<usize>,
8
+ next: Option<usize>,
9
+ }
10
+
11
+ pub struct LruCache {
12
+ capacity: usize,
13
+ map: HashMap<String, usize>,
14
+ nodes: Vec<LruNode>,
15
+ free_nodes: Vec<usize>,
16
+ head: Option<usize>, // Most Recently Used
17
+ tail: Option<usize>, // Least Recently Used
18
+ hits: u64,
19
+ misses: u64,
20
+ }
21
+
22
+ impl LruCache {
23
+ pub fn new(capacity: usize) -> Self {
24
+ Self {
25
+ capacity,
26
+ map: HashMap::with_capacity(capacity),
27
+ nodes: Vec::with_capacity(capacity),
28
+ free_nodes: Vec::new(),
29
+ head: None,
30
+ tail: None,
31
+ hits: 0,
32
+ misses: 0,
33
+ }
34
+ }
35
+
36
+ fn detach(&mut self, node_idx: usize) {
37
+ let prev = self.nodes[node_idx].prev;
38
+ let next = self.nodes[node_idx].next;
39
+
40
+ if let Some(p) = prev {
41
+ self.nodes[p].next = next;
42
+ } else {
43
+ self.head = next;
44
+ }
45
+
46
+ if let Some(n) = next {
47
+ self.nodes[n].prev = prev;
48
+ } else {
49
+ self.tail = prev;
50
+ }
51
+
52
+ self.nodes[node_idx].prev = None;
53
+ self.nodes[node_idx].next = None;
54
+ }
55
+
56
+ fn attach_to_head(&mut self, node_idx: usize) {
57
+ self.nodes[node_idx].prev = None;
58
+ self.nodes[node_idx].next = self.head;
59
+
60
+ if let Some(h) = self.head {
61
+ self.nodes[h].prev = Some(node_idx);
62
+ } else {
63
+ self.tail = Some(node_idx);
64
+ }
65
+
66
+ self.head = Some(node_idx);
67
+ }
68
+
69
+ fn move_to_head(&mut self, node_idx: usize) {
70
+ if self.head == Some(node_idx) {
71
+ return;
72
+ }
73
+ self.detach(node_idx);
74
+ self.attach_to_head(node_idx);
75
+ }
76
+
77
+ fn remove_node(&mut self, node_idx: usize) -> (String, CacheEntry) {
78
+ self.detach(node_idx);
79
+ self.free_nodes.push(node_idx);
80
+ let key = std::mem::take(&mut self.nodes[node_idx].key);
81
+ let entry = std::mem::replace(&mut self.nodes[node_idx].entry, CacheEntry::new(Vec::new(), None));
82
+ (key, entry)
83
+ }
84
+ }
85
+
86
+ impl CacheImpl for LruCache {
87
+ fn get(&mut self, key: &str) -> Option<Vec<u8>> {
88
+ if let Some(&node_idx) = self.map.get(key) {
89
+ if self.nodes[node_idx].entry.is_expired() {
90
+ self.remove_node(node_idx);
91
+ self.map.remove(key);
92
+ self.misses += 1;
93
+ None
94
+ } else {
95
+ self.move_to_head(node_idx);
96
+ self.hits += 1;
97
+ Some(self.nodes[node_idx].entry.value.clone())
98
+ }
99
+ } else {
100
+ self.misses += 1;
101
+ None
102
+ }
103
+ }
104
+
105
+ fn set(&mut self, key: &str, value: Vec<u8>, ttl_ms: Option<u64>) -> Option<Vec<u8>> {
106
+ if let Some(&node_idx) = self.map.get(key) {
107
+ // Key exists, update it
108
+ let old_value = std::mem::replace(
109
+ &mut self.nodes[node_idx].entry,
110
+ CacheEntry::new(value, ttl_ms),
111
+ ).value;
112
+ self.move_to_head(node_idx);
113
+ Some(old_value)
114
+ } else {
115
+ // If full, evict the LRU (tail)
116
+ if self.map.len() >= self.capacity && self.capacity > 0 {
117
+ if let Some(t_idx) = self.tail {
118
+ let (evicted_key, _) = self.remove_node(t_idx);
119
+ self.map.remove(&evicted_key);
120
+ }
121
+ }
122
+
123
+ // Insert new entry
124
+ let node_idx = if let Some(idx) = self.free_nodes.pop() {
125
+ self.nodes[idx].key = key.to_string();
126
+ self.nodes[idx].entry = CacheEntry::new(value, ttl_ms);
127
+ idx
128
+ } else {
129
+ let idx = self.nodes.len();
130
+ self.nodes.push(LruNode {
131
+ key: key.to_string(),
132
+ entry: CacheEntry::new(value, ttl_ms),
133
+ prev: None,
134
+ next: None,
135
+ });
136
+ idx
137
+ };
138
+
139
+ self.attach_to_head(node_idx);
140
+ self.map.insert(key.to_string(), node_idx);
141
+ None
142
+ }
143
+ }
144
+
145
+ fn delete(&mut self, key: &str) -> bool {
146
+ if let Some(&node_idx) = self.map.get(key) {
147
+ self.remove_node(node_idx);
148
+ self.map.remove(key);
149
+ true
150
+ } else {
151
+ false
152
+ }
153
+ }
154
+
155
+ fn clear(&mut self) {
156
+ self.map.clear();
157
+ self.nodes.clear();
158
+ self.free_nodes.clear();
159
+ self.head = None;
160
+ self.tail = None;
161
+ self.hits = 0;
162
+ self.misses = 0;
163
+ }
164
+
165
+ fn stats(&self) -> CacheStats {
166
+ CacheStats {
167
+ hits: self.hits,
168
+ misses: self.misses,
169
+ capacity: self.capacity,
170
+ size: self.map.len(),
171
+ }
172
+ }
173
+
174
+ fn keys(&self) -> Vec<String> {
175
+ let mut result = Vec::new();
176
+ let mut curr = self.head;
177
+ while let Some(idx) = curr {
178
+ let node = &self.nodes[idx];
179
+ if !node.entry.is_expired() {
180
+ result.push(node.key.clone());
181
+ }
182
+ curr = node.next;
183
+ }
184
+ result
185
+ }
186
+ }
187
+
188
+ #[cfg(test)]
189
+ mod tests {
190
+ use super::*;
191
+
192
+ #[test]
193
+ fn test_lru_basic() {
194
+ let mut cache = LruCache::new(2);
195
+ cache.set("a", vec![1], None);
196
+ cache.set("b", vec![2], None);
197
+ assert_eq!(cache.get("a"), Some(vec![1]));
198
+ cache.set("c", vec![3], None); // should evict "b"
199
+ assert_eq!(cache.get("b"), None);
200
+ assert_eq!(cache.get("a"), Some(vec![1]));
201
+ assert_eq!(cache.get("c"), Some(vec![3]));
202
+ }
203
+
204
+ #[test]
205
+ fn test_lru_expiry() {
206
+ let mut cache = LruCache::new(2);
207
+ cache.set("a", vec![1], Some(10)); // 10ms
208
+ assert_eq!(cache.get("a"), Some(vec![1]));
209
+ std::thread::sleep(std::time::Duration::from_millis(15));
210
+ assert_eq!(cache.get("a"), None);
211
+ }
212
+ }
@@ -0,0 +1,42 @@
1
+ use std::time::{Duration, Instant};
2
+
3
+ #[derive(Debug, Clone)]
4
+ pub struct CacheStats {
5
+ pub hits: u64,
6
+ pub misses: u64,
7
+ pub capacity: usize,
8
+ pub size: usize,
9
+ }
10
+
11
+ pub struct CacheEntry {
12
+ pub value: Vec<u8>,
13
+ pub expires_at: Option<Instant>,
14
+ }
15
+
16
+ impl CacheEntry {
17
+ pub fn new(value: Vec<u8>, ttl_ms: Option<u64>) -> Self {
18
+ let expires_at = ttl_ms.map(|ms| Instant::now() + Duration::from_millis(ms));
19
+ Self { value, expires_at }
20
+ }
21
+
22
+ pub fn is_expired(&self) -> bool {
23
+ if let Some(expiry) = self.expires_at {
24
+ Instant::now() > expiry
25
+ } else {
26
+ false
27
+ }
28
+ }
29
+ }
30
+
31
+ pub trait CacheImpl: Send + Sync {
32
+ fn get(&mut self, key: &str) -> Option<Vec<u8>>;
33
+ fn set(&mut self, key: &str, value: Vec<u8>, ttl_ms: Option<u64>) -> Option<Vec<u8>>;
34
+ fn delete(&mut self, key: &str) -> bool;
35
+ fn clear(&mut self);
36
+ fn stats(&self) -> CacheStats;
37
+ fn keys(&self) -> Vec<String>;
38
+ }
39
+
40
+ pub mod lru;
41
+ pub mod arc;
42
+ pub mod tinylfu;