1use crate::domain::{DomainError, DomainResult};
7use std::{
8 alloc::{Layout, alloc, dealloc, realloc},
9 ptr::NonNull,
10};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
14pub enum AllocatorBackend {
15 #[default]
17 System,
18 #[cfg(feature = "jemalloc")]
20 Jemalloc,
21 #[cfg(feature = "mimalloc")]
23 Mimalloc,
24}
25
26impl AllocatorBackend {
27 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 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
57pub struct SimdAllocator {
59 #[allow(dead_code)] backend: AllocatorBackend,
61}
62
63impl SimdAllocator {
64 pub fn new() -> Self {
66 Self {
67 backend: AllocatorBackend::current(),
68 }
69 }
70
71 pub fn with_backend(backend: AllocatorBackend) -> Self {
73 Self { backend }
74 }
75
76 pub unsafe fn alloc_aligned(&self, size: usize, alignment: usize) -> DomainResult<NonNull<u8>> {
81 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 #[cfg(feature = "jemalloc")]
94 if matches!(self.backend, AllocatorBackend::Jemalloc) {
95 return unsafe { self.jemalloc_alloc_aligned(size, alignment) };
96 }
97
98 #[cfg(feature = "mimalloc")]
100 if matches!(self.backend, AllocatorBackend::Mimalloc) {
101 return unsafe { self.mimalloc_alloc_aligned(size, alignment) };
102 }
103
104 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 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 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 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 #[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 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 #[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 pub fn stats(&self) -> AllocatorStats {
283 #[cfg(feature = "jemalloc")]
284 if matches!(self.backend, AllocatorBackend::Jemalloc) {
285 return self.jemalloc_stats();
286 }
287
288 AllocatorStats::default()
290 }
291
292 #[cfg(feature = "jemalloc")]
293 fn jemalloc_stats(&self) -> AllocatorStats {
294 use tikv_jemalloc_ctl::{epoch, stats};
295
296 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 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#[derive(Debug, Clone, Default)]
324pub struct AllocatorStats {
325 pub allocated_bytes: usize,
327 pub resident_bytes: usize,
329 pub metadata_bytes: usize,
331 pub backend: AllocatorBackend,
333}
334
335static GLOBAL_ALLOCATOR: std::sync::OnceLock<SimdAllocator> = std::sync::OnceLock::new();
337
338pub fn global_allocator() -> &'static SimdAllocator {
340 GLOBAL_ALLOCATOR.get_or_init(SimdAllocator::new)
341}
342
343pub 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 for alignment in [16, 32, 64, 128, 256].iter() {
379 let ptr = allocator.alloc_aligned(1024, *alignment).unwrap();
380
381 assert_eq!(
383 ptr.as_ptr() as usize % alignment,
384 0,
385 "Pointer not aligned to {} bytes",
386 alignment
387 );
388
389 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 let ptr = allocator.alloc_aligned(initial_size, alignment).unwrap();
407 let layout = Layout::from_size_align(initial_size, alignment).unwrap();
408
409 std::ptr::write_bytes(ptr.as_ptr(), 0xAB, initial_size);
411
412 let new_ptr = allocator.realloc_aligned(ptr, layout, new_size).unwrap();
414
415 assert_eq!(
417 new_ptr.as_ptr() as usize % alignment,
418 0,
419 "Reallocated pointer not aligned"
420 );
421
422 let first_byte = std::ptr::read(new_ptr.as_ptr());
424 assert_eq!(first_byte, 0xAB, "Data not preserved during reallocation");
425
426 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 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 let layout = Layout::from_size_align(1024 * 1024, 64).unwrap();
447 allocator.dealloc_aligned(ptr, layout);
448 }
449 }
450}