pjson_rs/parser/
allocator.rs

1//! Custom allocator support for high-performance buffer management
2//!
3//! This module provides abstraction over different memory allocators
4//! (system, jemalloc, mimalloc) with SIMD-aware alignment support.
5
6use crate::domain::{DomainError, DomainResult};
7use std::{
8    alloc::{Layout, alloc, dealloc, realloc},
9    ptr::NonNull,
10};
11
12/// Memory allocator backend
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
14pub enum AllocatorBackend {
15    /// System allocator (default)
16    #[default]
17    System,
18    /// Jemalloc allocator (high-performance, good for heavy allocation workloads)
19    #[cfg(feature = "jemalloc")]
20    Jemalloc,
21    /// Mimalloc allocator (Microsoft's allocator, good for multi-threaded workloads)
22    #[cfg(feature = "mimalloc")]
23    Mimalloc,
24}
25
26impl AllocatorBackend {
27    /// Get the current active allocator
28    pub fn current() -> Self {
29        #[cfg(feature = "jemalloc")]
30        {
31            Self::Jemalloc
32        }
33
34        #[cfg(all(feature = "mimalloc", not(feature = "jemalloc")))]
35        {
36            Self::Mimalloc
37        }
38
39        #[cfg(all(not(feature = "jemalloc"), not(feature = "mimalloc")))]
40        {
41            Self::System
42        }
43    }
44
45    /// Get allocator name for debugging
46    pub fn name(&self) -> &'static str {
47        match self {
48            Self::System => "system",
49            #[cfg(feature = "jemalloc")]
50            Self::Jemalloc => "jemalloc",
51            #[cfg(feature = "mimalloc")]
52            Self::Mimalloc => "mimalloc",
53        }
54    }
55}
56
57/// SIMD-aware memory allocator with support for different backends
58pub struct SimdAllocator {
59    backend: AllocatorBackend,
60}
61
62impl SimdAllocator {
63    /// Create a new allocator with the current backend
64    pub fn new() -> Self {
65        Self {
66            backend: AllocatorBackend::current(),
67        }
68    }
69
70    /// Create allocator with specific backend
71    pub fn with_backend(backend: AllocatorBackend) -> Self {
72        Self { backend }
73    }
74
75    /// Allocate aligned memory for SIMD operations
76    ///
77    /// # Safety
78    /// Returns raw pointer that must be properly deallocated
79    pub unsafe fn alloc_aligned(&self, size: usize, alignment: usize) -> DomainResult<NonNull<u8>> {
80        // Validate alignment
81        if !alignment.is_power_of_two() {
82            return Err(DomainError::InvalidInput(format!(
83                "Alignment {} is not a power of 2",
84                alignment
85            )));
86        }
87
88        let layout = Layout::from_size_align(size, alignment)
89            .map_err(|e| DomainError::InvalidInput(format!("Invalid layout: {}", e)))?;
90
91        // Use jemalloc-specific aligned allocation if available
92        #[cfg(feature = "jemalloc")]
93        if matches!(self.backend, AllocatorBackend::Jemalloc) {
94            return unsafe { self.jemalloc_alloc_aligned(size, alignment) };
95        }
96
97        // Use mimalloc-specific aligned allocation if available
98        #[cfg(feature = "mimalloc")]
99        if matches!(self.backend, AllocatorBackend::Mimalloc) {
100            return unsafe { self.mimalloc_alloc_aligned(size, alignment) };
101        }
102
103        // Fall back to system allocator
104        // Safety: layout is valid as checked above
105        unsafe {
106            let ptr = alloc(layout);
107            if ptr.is_null() {
108                return Err(DomainError::ResourceExhausted(format!(
109                    "Failed to allocate {} bytes with alignment {}",
110                    size, alignment
111                )));
112            }
113
114            Ok(NonNull::new_unchecked(ptr))
115        }
116    }
117
118    /// Reallocate aligned memory
119    ///
120    /// # Safety
121    /// Caller must ensure ptr was allocated with same alignment
122    pub unsafe fn realloc_aligned(
123        &self,
124        ptr: NonNull<u8>,
125        old_layout: Layout,
126        new_size: usize,
127    ) -> DomainResult<NonNull<u8>> {
128        let _new_layout = Layout::from_size_align(new_size, old_layout.align())
129            .map_err(|e| DomainError::InvalidInput(format!("Invalid layout: {}", e)))?;
130
131        #[cfg(feature = "jemalloc")]
132        if matches!(self.backend, AllocatorBackend::Jemalloc) {
133            return unsafe { self.jemalloc_realloc_aligned(ptr, old_layout, new_size) };
134        }
135
136        #[cfg(feature = "mimalloc")]
137        if matches!(self.backend, AllocatorBackend::Mimalloc) {
138            return unsafe { self.mimalloc_realloc_aligned(ptr, old_layout, new_size) };
139        }
140
141        // System allocator realloc
142        unsafe {
143            let new_ptr = realloc(ptr.as_ptr(), old_layout, new_size);
144            if new_ptr.is_null() {
145                return Err(DomainError::ResourceExhausted(format!(
146                    "Failed to reallocate to {} bytes",
147                    new_size
148                )));
149            }
150
151            Ok(NonNull::new_unchecked(new_ptr))
152        }
153    }
154
155    /// Deallocate aligned memory
156    ///
157    /// # Safety
158    /// Caller must ensure ptr was allocated with same layout
159    pub unsafe fn dealloc_aligned(&self, ptr: NonNull<u8>, layout: Layout) {
160        #[cfg(feature = "jemalloc")]
161        if matches!(self.backend, AllocatorBackend::Jemalloc) {
162            unsafe { self.jemalloc_dealloc_aligned(ptr, layout) };
163            return;
164        }
165
166        #[cfg(feature = "mimalloc")]
167        if matches!(self.backend, AllocatorBackend::Mimalloc) {
168            unsafe { self.mimalloc_dealloc_aligned(ptr, layout) };
169            return;
170        }
171
172        unsafe {
173            dealloc(ptr.as_ptr(), layout);
174        }
175    }
176
177    // Jemalloc-specific implementations
178    #[cfg(feature = "jemalloc")]
179    unsafe fn jemalloc_alloc_aligned(
180        &self,
181        size: usize,
182        alignment: usize,
183    ) -> DomainResult<NonNull<u8>> {
184        use tikv_jemalloc_sys as jemalloc;
185
186        // MALLOCX_ALIGN macro equivalent
187        let align_flag = alignment.trailing_zeros() as i32;
188        let ptr = unsafe { jemalloc::mallocx(size, align_flag) };
189        if ptr.is_null() {
190            return Err(DomainError::ResourceExhausted(format!(
191                "Jemalloc failed to allocate {} bytes with alignment {}",
192                size, alignment
193            )));
194        }
195
196        Ok(unsafe { NonNull::new_unchecked(ptr as *mut u8) })
197    }
198
199    #[cfg(feature = "jemalloc")]
200    unsafe fn jemalloc_realloc_aligned(
201        &self,
202        ptr: NonNull<u8>,
203        _old_layout: Layout,
204        new_size: usize,
205    ) -> DomainResult<NonNull<u8>> {
206        use tikv_jemalloc_sys as jemalloc;
207
208        let alignment = _old_layout.align();
209        let align_flag = alignment.trailing_zeros() as i32;
210        let new_ptr = unsafe { jemalloc::rallocx(ptr.as_ptr() as *mut _, new_size, align_flag) };
211
212        if new_ptr.is_null() {
213            return Err(DomainError::ResourceExhausted(format!(
214                "Jemalloc failed to reallocate to {} bytes",
215                new_size
216            )));
217        }
218
219        Ok(unsafe { NonNull::new_unchecked(new_ptr as *mut u8) })
220    }
221
222    #[cfg(feature = "jemalloc")]
223    unsafe fn jemalloc_dealloc_aligned(&self, ptr: NonNull<u8>, layout: Layout) {
224        use tikv_jemalloc_sys as jemalloc;
225
226        let align_flag = layout.align().trailing_zeros() as i32;
227        unsafe { jemalloc::dallocx(ptr.as_ptr() as *mut _, align_flag) };
228    }
229
230    // Mimalloc-specific implementations
231    #[cfg(feature = "mimalloc")]
232    unsafe fn mimalloc_alloc_aligned(
233        &self,
234        size: usize,
235        alignment: usize,
236    ) -> DomainResult<NonNull<u8>> {
237        use libmimalloc_sys as mi;
238
239        let ptr = unsafe { mi::mi_malloc_aligned(size, alignment) };
240        if ptr.is_null() {
241            return Err(DomainError::ResourceExhausted(format!(
242                "Mimalloc failed to allocate {} bytes with alignment {}",
243                size, alignment
244            )));
245        }
246
247        Ok(unsafe { NonNull::new_unchecked(ptr as *mut u8) })
248    }
249
250    #[cfg(feature = "mimalloc")]
251    unsafe fn mimalloc_realloc_aligned(
252        &self,
253        ptr: NonNull<u8>,
254        _old_layout: Layout,
255        new_size: usize,
256    ) -> DomainResult<NonNull<u8>> {
257        use libmimalloc_sys as mi;
258
259        let alignment = _old_layout.align();
260        let new_ptr =
261            unsafe { mi::mi_realloc_aligned(ptr.as_ptr() as *mut _, new_size, alignment) };
262
263        if new_ptr.is_null() {
264            return Err(DomainError::ResourceExhausted(format!(
265                "Mimalloc failed to reallocate to {} bytes",
266                new_size
267            )));
268        }
269
270        Ok(unsafe { NonNull::new_unchecked(new_ptr as *mut u8) })
271    }
272
273    #[cfg(feature = "mimalloc")]
274    unsafe fn mimalloc_dealloc_aligned(&self, ptr: NonNull<u8>, _layout: Layout) {
275        use libmimalloc_sys as mi;
276
277        unsafe { mi::mi_free(ptr.as_ptr() as *mut _) };
278    }
279
280    /// Get allocator statistics (if available)
281    pub fn stats(&self) -> AllocatorStats {
282        #[cfg(feature = "jemalloc")]
283        if matches!(self.backend, AllocatorBackend::Jemalloc) {
284            return self.jemalloc_stats();
285        }
286
287        // Default stats for other allocators
288        AllocatorStats::default()
289    }
290
291    #[cfg(feature = "jemalloc")]
292    fn jemalloc_stats(&self) -> AllocatorStats {
293        use tikv_jemalloc_ctl::{epoch, stats};
294
295        // Update the statistics cache
296        if let Err(e) = epoch::mib().map(|mib| mib.advance()) {
297            eprintln!("Failed to advance jemalloc epoch: {}", e);
298            return AllocatorStats::default();
299        }
300
301        // Query statistics
302        let allocated = stats::allocated::read().unwrap_or(0);
303        let resident = stats::resident::read().unwrap_or(0);
304        let metadata = stats::metadata::read().unwrap_or(0);
305
306        AllocatorStats {
307            allocated_bytes: allocated,
308            resident_bytes: resident,
309            metadata_bytes: metadata,
310            backend: self.backend,
311        }
312    }
313}
314
315impl Default for SimdAllocator {
316    fn default() -> Self {
317        Self::new()
318    }
319}
320
321/// Allocator statistics
322#[derive(Debug, Clone, Default)]
323pub struct AllocatorStats {
324    /// Total allocated bytes
325    pub allocated_bytes: usize,
326    /// Resident memory in bytes
327    pub resident_bytes: usize,
328    /// Metadata overhead in bytes
329    pub metadata_bytes: usize,
330    /// Backend being used
331    pub backend: AllocatorBackend,
332}
333
334/// Global allocator instance
335static GLOBAL_ALLOCATOR: std::sync::OnceLock<SimdAllocator> = std::sync::OnceLock::new();
336
337/// Get global SIMD allocator
338pub fn global_allocator() -> &'static SimdAllocator {
339    GLOBAL_ALLOCATOR.get_or_init(SimdAllocator::new)
340}
341
342/// Initialize global allocator with specific backend
343pub fn initialize_global_allocator(backend: AllocatorBackend) -> DomainResult<()> {
344    GLOBAL_ALLOCATOR
345        .set(SimdAllocator::with_backend(backend))
346        .map_err(|_| {
347            DomainError::InternalError("Global allocator already initialized".to_string())
348        })?;
349    Ok(())
350}
351
352#[cfg(test)]
353mod tests {
354    use super::*;
355
356    #[test]
357    fn test_allocator_backend_detection() {
358        let backend = AllocatorBackend::current();
359        println!("Current allocator backend: {}", backend.name());
360
361        #[cfg(feature = "jemalloc")]
362        assert_eq!(backend, AllocatorBackend::Jemalloc);
363
364        #[cfg(all(feature = "mimalloc", not(feature = "jemalloc")))]
365        assert_eq!(backend, AllocatorBackend::Mimalloc);
366
367        #[cfg(all(not(feature = "jemalloc"), not(feature = "mimalloc")))]
368        assert_eq!(backend, AllocatorBackend::System);
369    }
370
371    #[test]
372    fn test_aligned_allocation() {
373        let allocator = SimdAllocator::new();
374
375        unsafe {
376            // Test various alignments
377            for alignment in [16, 32, 64, 128, 256].iter() {
378                let ptr = allocator.alloc_aligned(1024, *alignment).unwrap();
379
380                // Verify alignment
381                assert_eq!(
382                    ptr.as_ptr() as usize % alignment,
383                    0,
384                    "Pointer not aligned to {} bytes",
385                    alignment
386                );
387
388                // Clean up
389                let layout = Layout::from_size_align(1024, *alignment).unwrap();
390                allocator.dealloc_aligned(ptr, layout);
391            }
392        }
393    }
394
395    #[test]
396    fn test_reallocation() {
397        let allocator = SimdAllocator::new();
398
399        unsafe {
400            let alignment = 64;
401            let initial_size = 1024;
402            let new_size = 2048;
403
404            // Initial allocation
405            let ptr = allocator.alloc_aligned(initial_size, alignment).unwrap();
406            let layout = Layout::from_size_align(initial_size, alignment).unwrap();
407
408            // Write some data
409            std::ptr::write_bytes(ptr.as_ptr(), 0xAB, initial_size);
410
411            // Reallocate
412            let new_ptr = allocator.realloc_aligned(ptr, layout, new_size).unwrap();
413
414            // Verify alignment is preserved
415            assert_eq!(
416                new_ptr.as_ptr() as usize % alignment,
417                0,
418                "Reallocated pointer not aligned"
419            );
420
421            // Verify data is preserved
422            let first_byte = std::ptr::read(new_ptr.as_ptr());
423            assert_eq!(first_byte, 0xAB, "Data not preserved during reallocation");
424
425            // Clean up
426            let new_layout = Layout::from_size_align(new_size, alignment).unwrap();
427            allocator.dealloc_aligned(new_ptr, new_layout);
428        }
429    }
430
431    #[cfg(feature = "jemalloc")]
432    #[test]
433    fn test_jemalloc_stats() {
434        let allocator = SimdAllocator::with_backend(AllocatorBackend::Jemalloc);
435
436        // Allocate some memory
437        unsafe {
438            let ptr = allocator.alloc_aligned(1024 * 1024, 64).unwrap();
439
440            let stats = allocator.stats();
441            assert!(stats.allocated_bytes > 0);
442            println!("Jemalloc stats: {:?}", stats);
443
444            // Clean up
445            let layout = Layout::from_size_align(1024 * 1024, 64).unwrap();
446            allocator.dealloc_aligned(ptr, layout);
447        }
448    }
449}