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