Skip to main content

linkrs_core/
arena.rs

1//! Arena Allocator Module
2//!
3//! Provides efficient memory allocation for temporary data structures and batch operations
4//! using the bumpalo crate as the underlying allocator.
5//!
6//! ## Overview
7//!
8//! Arena allocation is a memory management strategy where all allocations are made from
9//! a contiguous region of memory (the "arena"), and all allocations can be freed at once
10//! by resetting or dropping the arena. This provides:
11//!
12//! - **O(1) allocation**: Simply bump a pointer forward
13//! - **O(1) deallocation**: Reset the entire arena at once
14//! - **Cache locality**: Allocations are contiguous in memory
15//! - **No fragmentation**: All allocations are from contiguous chunks
16//!
17//! ## When to Use
18//!
19//! Arena allocation is ideal for:
20//!
21//! 1. **Query execution**: Temporary results, intermediate values
22//! 2. **Expression evaluation**: Temporary values during computation
23//! 3. **Batch processing**: Processing large datasets with temporary buffers
24//! 4. **Graph traversal**: Path accumulation, neighbor expansion
25//! 5. **Parsing**: AST nodes with the same lifetime
26//!
27//! ## When NOT to Use
28//!
29//! Arena allocation is NOT suitable for:
30//!
31//! - Long-lived data structures (use `Box` or `Vec` instead)
32//! - Data that needs individual deallocation
33//! - Small, infrequent allocations
34//!
35//! ## Usage Examples
36//!
37//! ### Basic Usage
38//!
39//! ```rust,ignore
40//! use linkrs::utils::Arena;
41//!
42//! let mut arena = Arena::new();
43//!
44//! // Allocate values
45//! let a = arena.alloc(42);
46//! let b = arena.alloc("hello");
47//!
48//! // Allocate slices
49//! let slice = arena.alloc_slice(&[1, 2, 3, 4, 5]);
50//!
51//! // Allocate strings
52//! let s = arena.alloc_str("world");
53//!
54//! // Reset to free all allocations at once
55//! arena.reset();
56//! ```
57//!
58//! ### With ArenaVec
59//!
60//! ```rust,ignore
61//! use linkrs::utils::{Arena, ArenaVec};
62//!
63//! let arena = Arena::new();
64//! let mut vec = ArenaVec::new(arena.inner());
65//!
66//! vec.push(1);
67//! vec.push(2);
68//! vec.push(3);
69//!
70//! // All elements are allocated in the arena
71//! ```
72
73use bumpalo::Bump;
74
75/// Arena allocator for efficient batch allocations.
76///
77/// This is a wrapper around `bumpalo::Bump` providing a clean API
78/// for arena-based memory allocation.
79#[derive(Debug)]
80pub struct Arena {
81    inner: Bump,
82}
83
84impl Arena {
85    pub fn new() -> Self {
86        Self { inner: Bump::new() }
87    }
88
89    pub fn with_capacity(capacity: usize) -> Self {
90        Self {
91            inner: Bump::with_capacity(capacity),
92        }
93    }
94
95    pub fn alloc<T>(&self, value: T) -> &mut T {
96        self.inner.alloc(value)
97    }
98
99    pub fn alloc_slice<T: Copy>(&self, slice: &[T]) -> &mut [T] {
100        self.inner.alloc_slice_copy(slice)
101    }
102
103    pub fn alloc_str(&self, s: &str) -> &mut str {
104        self.inner.alloc_str(s)
105    }
106
107    pub fn allocated_bytes(&self) -> usize {
108        self.inner.allocated_bytes()
109    }
110
111    pub fn reset(&mut self) {
112        self.inner.reset();
113    }
114
115    pub fn inner(&self) -> &Bump {
116        &self.inner
117    }
118}
119
120impl Default for Arena {
121    fn default() -> Self {
122        Self::new()
123    }
124}
125
126/// Arena pool for multi-threaded allocation.
127///
128/// Provides multiple arenas that can be distributed across threads
129/// for concurrent allocation without contention.
130pub struct ArenaPool {
131    arenas: Vec<Arena>,
132    current: std::sync::atomic::AtomicUsize,
133}
134
135impl ArenaPool {
136    pub fn new(arena_count: usize) -> Self {
137        let arenas = (0..arena_count).map(|_| Arena::new()).collect();
138        Self {
139            arenas,
140            current: std::sync::atomic::AtomicUsize::new(0),
141        }
142    }
143
144    pub fn with_capacity(arena_count: usize, capacity: usize) -> Self {
145        let arenas = (0..arena_count)
146            .map(|_| Arena::with_capacity(capacity))
147            .collect();
148        Self {
149            arenas,
150            current: std::sync::atomic::AtomicUsize::new(0),
151        }
152    }
153
154    pub fn get_arena(&self) -> &Arena {
155        use std::sync::atomic::Ordering;
156        let idx = self.current.fetch_add(1, Ordering::Relaxed) % self.arenas.len();
157        &self.arenas[idx]
158    }
159
160    pub fn reset_all(&mut self) {
161        for arena in &mut self.arenas {
162            arena.reset();
163        }
164    }
165
166    pub fn total_allocated(&self) -> usize {
167        self.arenas.iter().map(|a| a.allocated_bytes()).sum()
168    }
169}
170
171/// Arena-based string tokenizer.
172pub struct ArenaTokenizer<'a> {
173    arena: &'a Bump,
174}
175
176impl<'a> ArenaTokenizer<'a> {
177    pub fn new(arena: &'a Bump) -> Self {
178        Self { arena }
179    }
180
181    pub fn tokenize(&self, text: &str) -> Vec<&'a str> {
182        text.split_whitespace()
183            .map(|word| self.arena.alloc_str(word) as &str)
184            .collect()
185    }
186
187    pub fn tokenize_with_sep(&self, text: &str, sep: char) -> Vec<&'a str> {
188        text.split(sep)
189            .filter(|word| !word.is_empty())
190            .map(|word| self.arena.alloc_str(word) as &str)
191            .collect()
192    }
193}
194
195/// Arena-based temporary vector.
196pub struct ArenaVec<'a, T> {
197    arena: &'a Bump,
198    items: Vec<&'a mut T>,
199}
200
201impl<'a, T> ArenaVec<'a, T> {
202    pub fn new(arena: &'a Bump) -> Self {
203        Self {
204            arena,
205            items: Vec::new(),
206        }
207    }
208
209    pub fn push(&mut self, value: T) {
210        let item = self.arena.alloc(value);
211        self.items.push(item);
212    }
213
214    pub fn len(&self) -> usize {
215        self.items.len()
216    }
217
218    pub fn is_empty(&self) -> bool {
219        self.items.is_empty()
220    }
221
222    pub fn get(&self, index: usize) -> Option<&T> {
223        self.items.get(index).map(|item| &**item)
224    }
225
226    pub fn iter(&self) -> impl Iterator<Item = &T> {
227        self.items.iter().map(|item| &**item)
228    }
229}
230
231/// Arena-based string builder.
232pub struct ArenaStringBuilder<'a> {
233    arena: &'a Bump,
234    buffer: Vec<&'a str>,
235}
236
237impl<'a> ArenaStringBuilder<'a> {
238    pub fn new(arena: &'a Bump) -> Self {
239        Self {
240            arena,
241            buffer: Vec::new(),
242        }
243    }
244
245    pub fn append(&mut self, s: &str) {
246        let slice = self.arena.alloc_str(s);
247        self.buffer.push(slice);
248    }
249
250    pub fn build(&self) -> String {
251        self.buffer.concat()
252    }
253
254    pub fn slices(&self) -> &[&'a str] {
255        &self.buffer
256    }
257}
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262
263    #[test]
264    fn test_arena_basic() {
265        let mut arena = Arena::new();
266
267        let a = arena.alloc(42);
268        let b = arena.alloc("hello");
269
270        assert_eq!(*a, 42);
271        assert_eq!(*b, "hello");
272
273        arena.reset();
274        let c = arena.alloc(100);
275        assert_eq!(*c, 100);
276    }
277
278    #[test]
279    fn test_arena_slice() {
280        let arena = Arena::new();
281
282        let slice = arena.alloc_slice(&[1, 2, 3, 4, 5]);
283        assert_eq!(slice.len(), 5);
284        assert_eq!(slice[0], 1);
285        assert_eq!(slice[4], 5);
286    }
287
288    #[test]
289    fn test_arena_string() {
290        let arena = Arena::new();
291
292        let s = arena.alloc_str("hello world");
293        assert_eq!(s, "hello world");
294    }
295
296    #[test]
297    fn test_arena_pool() {
298        let pool = ArenaPool::new(4);
299
300        let arena1 = pool.get_arena();
301        let _ = arena1.alloc(1);
302
303        let arena2 = pool.get_arena();
304        let _ = arena2.alloc(2);
305
306        assert!(pool.total_allocated() > 0);
307    }
308
309    #[test]
310    fn test_arena_tokenizer() {
311        let arena = Arena::new();
312        let tokenizer = ArenaTokenizer::new(arena.inner());
313
314        let tokens = tokenizer.tokenize("hello world rust");
315        assert_eq!(tokens.len(), 3);
316        assert_eq!(tokens[0], "hello");
317        assert_eq!(tokens[1], "world");
318        assert_eq!(tokens[2], "rust");
319    }
320
321    #[test]
322    fn test_arena_vec() {
323        let arena = Arena::new();
324        let mut vec = ArenaVec::new(arena.inner());
325
326        vec.push(1);
327        vec.push(2);
328        vec.push(3);
329
330        assert_eq!(vec.len(), 3);
331        assert_eq!(vec.get(0), Some(&1));
332        assert_eq!(vec.get(1), Some(&2));
333        assert_eq!(vec.get(2), Some(&3));
334    }
335
336    #[test]
337    fn test_arena_string_builder() {
338        let arena = Arena::new();
339        let mut builder = ArenaStringBuilder::new(arena.inner());
340
341        builder.append("hello");
342        builder.append(" ");
343        builder.append("world");
344
345        assert_eq!(builder.build(), "hello world");
346    }
347}