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 backend: AllocatorBackend,
60}
61
62impl SimdAllocator {
63 pub fn new() -> Self {
65 Self {
66 backend: AllocatorBackend::current(),
67 }
68 }
69
70 pub fn with_backend(backend: AllocatorBackend) -> Self {
72 Self { backend }
73 }
74
75 pub unsafe fn alloc_aligned(&self, size: usize, alignment: usize) -> DomainResult<NonNull<u8>> {
80 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 #[cfg(feature = "jemalloc")]
93 if matches!(self.backend, AllocatorBackend::Jemalloc) {
94 return unsafe { self.jemalloc_alloc_aligned(size, alignment) };
95 }
96
97 #[cfg(feature = "mimalloc")]
99 if matches!(self.backend, AllocatorBackend::Mimalloc) {
100 return unsafe { self.mimalloc_alloc_aligned(size, alignment) };
101 }
102
103 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 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 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 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 #[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 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 #[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 pub fn stats(&self) -> AllocatorStats {
282 #[cfg(feature = "jemalloc")]
283 if matches!(self.backend, AllocatorBackend::Jemalloc) {
284 return self.jemalloc_stats();
285 }
286
287 AllocatorStats::default()
289 }
290
291 #[cfg(feature = "jemalloc")]
292 fn jemalloc_stats(&self) -> AllocatorStats {
293 use tikv_jemalloc_ctl::{epoch, stats};
294
295 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 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#[derive(Debug, Clone, Default)]
323pub struct AllocatorStats {
324 pub allocated_bytes: usize,
326 pub resident_bytes: usize,
328 pub metadata_bytes: usize,
330 pub backend: AllocatorBackend,
332}
333
334static GLOBAL_ALLOCATOR: std::sync::OnceLock<SimdAllocator> = std::sync::OnceLock::new();
336
337pub fn global_allocator() -> &'static SimdAllocator {
339 GLOBAL_ALLOCATOR.get_or_init(SimdAllocator::new)
340}
341
342pub 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 for alignment in [16, 32, 64, 128, 256].iter() {
378 let ptr = allocator.alloc_aligned(1024, *alignment).unwrap();
379
380 assert_eq!(
382 ptr.as_ptr() as usize % alignment,
383 0,
384 "Pointer not aligned to {} bytes",
385 alignment
386 );
387
388 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 let ptr = allocator.alloc_aligned(initial_size, alignment).unwrap();
406 let layout = Layout::from_size_align(initial_size, alignment).unwrap();
407
408 std::ptr::write_bytes(ptr.as_ptr(), 0xAB, initial_size);
410
411 let new_ptr = allocator.realloc_aligned(ptr, layout, new_size).unwrap();
413
414 assert_eq!(
416 new_ptr.as_ptr() as usize % alignment,
417 0,
418 "Reallocated pointer not aligned"
419 );
420
421 let first_byte = std::ptr::read(new_ptr.as_ptr());
423 assert_eq!(first_byte, 0xAB, "Data not preserved during reallocation");
424
425 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 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 let layout = Layout::from_size_align(1024 * 1024, 64).unwrap();
446 allocator.dealloc_aligned(ptr, layout);
447 }
448 }
449}