ruvector_core/
arena.rs

1//! Arena allocator for batch operations
2//!
3//! This module provides arena-based memory allocation to reduce allocation
4//! overhead in hot paths and improve memory locality.
5
6use std::alloc::{alloc, dealloc, Layout};
7use std::cell::RefCell;
8use std::ptr;
9
10/// Arena allocator for temporary allocations
11///
12/// Use this for batch operations where many temporary allocations
13/// are needed and can be freed all at once.
14pub struct Arena {
15    chunks: RefCell<Vec<Chunk>>,
16    chunk_size: usize,
17}
18
19struct Chunk {
20    data: *mut u8,
21    capacity: usize,
22    used: usize,
23}
24
25impl Arena {
26    /// Create a new arena with the specified chunk size
27    pub fn new(chunk_size: usize) -> Self {
28        Self {
29            chunks: RefCell::new(Vec::new()),
30            chunk_size,
31        }
32    }
33
34    /// Create an arena with a default 1MB chunk size
35    pub fn with_default_chunk_size() -> Self {
36        Self::new(1024 * 1024) // 1MB
37    }
38
39    /// Allocate a buffer of the specified size
40    pub fn alloc_vec<T>(&self, count: usize) -> ArenaVec<T> {
41        let size = count * std::mem::size_of::<T>();
42        let align = std::mem::align_of::<T>();
43
44        let ptr = self.alloc_raw(size, align);
45
46        ArenaVec {
47            ptr: ptr as *mut T,
48            len: 0,
49            capacity: count,
50            _phantom: std::marker::PhantomData,
51        }
52    }
53
54    /// Allocate raw bytes with specified alignment
55    fn alloc_raw(&self, size: usize, align: usize) -> *mut u8 {
56        let mut chunks = self.chunks.borrow_mut();
57
58        // Try to allocate from the last chunk
59        if let Some(chunk) = chunks.last_mut() {
60            // Align the current position
61            let current = chunk.used;
62            let aligned = (current + align - 1) & !(align - 1);
63            let needed = aligned + size;
64
65            if needed <= chunk.capacity {
66                chunk.used = needed;
67                return unsafe { chunk.data.add(aligned) };
68            }
69        }
70
71        // Need a new chunk
72        let chunk_size = self.chunk_size.max(size + align);
73        let layout = Layout::from_size_align(chunk_size, 64).unwrap();
74        let data = unsafe { alloc(layout) };
75
76        let aligned = align;
77        let chunk = Chunk {
78            data,
79            capacity: chunk_size,
80            used: aligned + size,
81        };
82
83        let ptr = unsafe { data.add(aligned) };
84        chunks.push(chunk);
85
86        ptr
87    }
88
89    /// Reset the arena, allowing reuse of allocated memory
90    pub fn reset(&self) {
91        let mut chunks = self.chunks.borrow_mut();
92        for chunk in chunks.iter_mut() {
93            chunk.used = 0;
94        }
95    }
96
97    /// Get total allocated bytes
98    pub fn allocated_bytes(&self) -> usize {
99        let chunks = self.chunks.borrow();
100        chunks.iter().map(|c| c.capacity).sum()
101    }
102
103    /// Get used bytes
104    pub fn used_bytes(&self) -> usize {
105        let chunks = self.chunks.borrow();
106        chunks.iter().map(|c| c.used).sum()
107    }
108}
109
110impl Drop for Arena {
111    fn drop(&mut self) {
112        let chunks = self.chunks.borrow();
113        for chunk in chunks.iter() {
114            let layout = Layout::from_size_align(chunk.capacity, 64).unwrap();
115            unsafe {
116                dealloc(chunk.data, layout);
117            }
118        }
119    }
120}
121
122/// Vector allocated from an arena
123pub struct ArenaVec<T> {
124    ptr: *mut T,
125    len: usize,
126    capacity: usize,
127    _phantom: std::marker::PhantomData<T>,
128}
129
130impl<T> ArenaVec<T> {
131    /// Push an element (panics if capacity exceeded)
132    pub fn push(&mut self, value: T) {
133        assert!(self.len < self.capacity, "ArenaVec capacity exceeded");
134        unsafe {
135            ptr::write(self.ptr.add(self.len), value);
136        }
137        self.len += 1;
138    }
139
140    /// Get length
141    pub fn len(&self) -> usize {
142        self.len
143    }
144
145    /// Check if empty
146    pub fn is_empty(&self) -> bool {
147        self.len == 0
148    }
149
150    /// Get capacity
151    pub fn capacity(&self) -> usize {
152        self.capacity
153    }
154
155    /// Get as slice
156    pub fn as_slice(&self) -> &[T] {
157        unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
158    }
159
160    /// Get as mutable slice
161    pub fn as_mut_slice(&mut self) -> &mut [T] {
162        unsafe { std::slice::from_raw_parts_mut(self.ptr, self.len) }
163    }
164}
165
166impl<T> std::ops::Deref for ArenaVec<T> {
167    type Target = [T];
168
169    fn deref(&self) -> &[T] {
170        self.as_slice()
171    }
172}
173
174impl<T> std::ops::DerefMut for ArenaVec<T> {
175    fn deref_mut(&mut self) -> &mut [T] {
176        self.as_mut_slice()
177    }
178}
179
180/// Thread-local arena for per-thread allocations
181thread_local! {
182    static THREAD_ARENA: RefCell<Arena> = RefCell::new(Arena::with_default_chunk_size());
183}
184
185/// Get the thread-local arena
186/// Note: Commented out due to lifetime issues with RefCell::borrow() escaping closure
187/// Use THREAD_ARENA.with(|arena| { ... }) directly instead
188/*
189pub fn thread_arena() -> impl std::ops::Deref<Target = Arena> {
190    THREAD_ARENA.with(|arena| {
191        arena.borrow()
192    })
193}
194*/
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    #[test]
201    fn test_arena_alloc() {
202        let arena = Arena::new(1024);
203
204        let mut vec1 = arena.alloc_vec::<f32>(10);
205        vec1.push(1.0);
206        vec1.push(2.0);
207        vec1.push(3.0);
208
209        assert_eq!(vec1.len(), 3);
210        assert_eq!(vec1[0], 1.0);
211        assert_eq!(vec1[1], 2.0);
212        assert_eq!(vec1[2], 3.0);
213    }
214
215    #[test]
216    fn test_arena_multiple_allocs() {
217        let arena = Arena::new(1024);
218
219        let vec1 = arena.alloc_vec::<u32>(100);
220        let vec2 = arena.alloc_vec::<u64>(50);
221        let vec3 = arena.alloc_vec::<f32>(200);
222
223        assert_eq!(vec1.capacity(), 100);
224        assert_eq!(vec2.capacity(), 50);
225        assert_eq!(vec3.capacity(), 200);
226    }
227
228    #[test]
229    fn test_arena_reset() {
230        let arena = Arena::new(1024);
231
232        {
233            let _vec1 = arena.alloc_vec::<f32>(100);
234            let _vec2 = arena.alloc_vec::<f32>(100);
235        }
236
237        let used_before = arena.used_bytes();
238        arena.reset();
239        let used_after = arena.used_bytes();
240
241        assert!(used_after < used_before);
242    }
243}