Skip to main content

malloc_best_effort/
lib.rs

1#![no_std]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4//! Best effort memory allocator for Rust
5//!
6//! Selects suitable allocator depending on target OS and architecture.
7//!
8//! ## Usage
9//! * Put to your `src/main.rs`:
10//! ```rust,ignore
11//! use malloc_best_effort::BEMalloc;
12//!
13//! #[global_allocator]
14//! static GLOBAL: BEMalloc = BEMalloc::new();
15//!
16//! fn main() {
17//!     BEMalloc::init();
18//!
19//!     // Rest of main
20//! }
21//! ```
22//!
23//! * Put to `build.rs` to workaround mimalloc build dependencies:
24//! ```rust,ignore
25//! use std::borrow::Cow;
26//! use std::env;
27//!
28//! fn main() {
29//!     let target_os = env::var("CARGO_CFG_TARGET_OS").expect("target_os not defined!");
30//!     let target_arch = env::var("CARGO_CFG_TARGET_ARCH").expect("target_arch not defined!"); // on armv6 we need to link with libatomic
31//!
32//!     if target_os == "linux" && target_arch == "arm" {
33//!         // Embrace the atomic capability library across various platforms.
34//!         // For instance, on certain platforms, llvm has relocated the atomic of the arm32 architecture to libclang_rt.builtins.a
35//!         // while some use libatomic.a, and others use libatomic_ops.a.
36//!         let atomic_name = match env::var("DEP_ATOMIC") {
37//!             Ok(atomic_name) => Cow::Owned(atomic_name),
38//!             Err(_) => Cow::Borrowed("atomic"),
39//!         };
40//!         println!("cargo:rustc-link-lib={atomic_name}");
41//!     }
42//!
43//!     // Link with libs needed on Windows
44//!     if target_os == "windows" {
45//!         // https://github.com/microsoft/mimalloc/blob/af21001f7a65eafb8fb16460b018ebf9d75e2ad8/CMakeLists.txt#L487
46//!
47//!         for lib in ["psapi", "shell32", "user32", "advapi32", "bcrypt"] {
48//!             println!("cargo:rustc-link-lib={lib}");
49//!         }
50//!     }
51//! }
52//! ```
53//!
54//! ## Feature flags
55#![doc = document_features::document_features!()]
56
57#[cfg(not(all(
58    target_os = "linux",
59    any(target_arch = "x86_64", target_arch = "aarch64",),
60    target_endian = "little"
61)))]
62mod mimalloc {
63    pub(crate) use mimalloc::MiMalloc as BEMallocImpl;
64
65    #[inline]
66    pub(crate) fn init_impl() {}
67}
68#[cfg(all(
69    target_os = "linux",
70    any(target_arch = "x86_64", target_arch = "aarch64",),
71    target_endian = "little"
72))]
73mod tcmalloc {
74    pub(crate) use tcmalloc_better::TCMalloc as BEMallocImpl;
75
76    #[cfg(feature = "std")]
77    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
78    #[inline]
79    pub(crate) fn init_impl() {
80        BEMallocImpl::process_background_actions_thread();
81    }
82
83    #[cfg(not(feature = "std"))]
84    #[cfg_attr(docsrs, doc(cfg(not(feature = "std"))))]
85    #[inline]
86    pub(crate) fn init_impl() {}
87}
88
89#[cfg(not(all(
90    target_os = "linux",
91    any(target_arch = "x86_64", target_arch = "aarch64",),
92    target_endian = "little"
93)))]
94use crate::mimalloc::BEMallocImpl;
95#[cfg(not(all(
96    target_os = "linux",
97    any(target_arch = "x86_64", target_arch = "aarch64",),
98    target_endian = "little"
99)))]
100use crate::mimalloc::init_impl;
101#[cfg(all(
102    target_os = "linux",
103    any(target_arch = "x86_64", target_arch = "aarch64",),
104    target_endian = "little"
105))]
106use crate::tcmalloc::BEMallocImpl;
107#[cfg(all(
108    target_os = "linux",
109    any(target_arch = "x86_64", target_arch = "aarch64",),
110    target_endian = "little"
111))]
112use crate::tcmalloc::init_impl;
113use core::alloc::{GlobalAlloc, Layout};
114
115/// A memory allocator that can be registered as the standard library’s default
116/// through the `#[global_allocator]` attribute.
117pub struct BEMalloc {
118    alloc_impl: BEMallocImpl,
119}
120
121unsafe impl GlobalAlloc for BEMalloc {
122    #[inline]
123    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
124        unsafe { self.alloc_impl.alloc(layout) }
125    }
126
127    #[inline]
128    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
129        unsafe { self.alloc_impl.dealloc(ptr, layout) }
130    }
131
132    #[inline]
133    unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
134        unsafe { self.alloc_impl.alloc_zeroed(layout) }
135    }
136
137    #[inline]
138    unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
139        unsafe { self.alloc_impl.realloc(ptr, layout, new_size) }
140    }
141}
142
143impl Default for BEMalloc {
144    #[inline]
145    fn default() -> Self {
146        Self::new()
147    }
148}
149
150impl BEMalloc {
151    /// Create a new instance of allocator.
152    #[inline]
153    pub const fn new() -> Self {
154        Self {
155            alloc_impl: BEMallocImpl,
156        }
157    }
158    /// Start an allocator background job. Should be called in `main()` function.
159    #[inline]
160    pub fn init() {
161        init_impl();
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168
169    #[test]
170    fn it_frees_allocated_memory() {
171        unsafe {
172            let layout = Layout::from_size_align(8, 8).unwrap();
173            let alloc = BEMalloc::new();
174
175            let ptr = alloc.alloc(layout);
176            alloc.dealloc(ptr, layout);
177        }
178    }
179
180    #[test]
181    fn it_frees_allocated_memory_with_init() {
182        unsafe {
183            let layout = Layout::from_size_align(8, 8).unwrap();
184            let alloc = BEMalloc::new();
185            BEMalloc::init();
186
187            let ptr = alloc.alloc(layout);
188            alloc.dealloc(ptr, layout);
189        }
190    }
191
192    #[test]
193    fn it_frees_allocated_big_memory() {
194        unsafe {
195            let layout = Layout::from_size_align(1 << 20, 32).unwrap();
196            let alloc = BEMalloc::new();
197
198            let ptr = alloc.alloc(layout);
199            alloc.dealloc(ptr, layout);
200        }
201    }
202
203    #[test]
204    fn it_frees_zero_allocated_memory() {
205        unsafe {
206            let layout = Layout::from_size_align(8, 8).unwrap();
207            let alloc = BEMalloc::new();
208
209            let ptr = alloc.alloc_zeroed(layout);
210            alloc.dealloc(ptr, layout);
211        }
212    }
213
214    #[test]
215    fn it_frees_zero_allocated_big_memory() {
216        unsafe {
217            let layout = Layout::from_size_align(1 << 20, 32).unwrap();
218            let alloc = BEMalloc::new();
219
220            let ptr = alloc.alloc_zeroed(layout);
221            alloc.dealloc(ptr, layout);
222        }
223    }
224
225    #[test]
226    fn it_frees_reallocated_memory() {
227        unsafe {
228            let layout = Layout::from_size_align(8, 8).unwrap();
229            let new_size = 16;
230            let new_layout = Layout::from_size_align(new_size, layout.align()).unwrap();
231            let alloc = BEMalloc::new();
232
233            let ptr = alloc.alloc(layout);
234            let ptr = alloc.realloc(ptr, layout, new_size);
235            alloc.dealloc(ptr, new_layout);
236        }
237    }
238
239    #[test]
240    fn it_frees_reallocated_big_memory() {
241        unsafe {
242            let layout = Layout::from_size_align(1 << 20, 32).unwrap();
243            let new_size = 2 << 20;
244            let new_layout = Layout::from_size_align(new_size, layout.align()).unwrap();
245            let alloc = BEMalloc::new();
246
247            let ptr = alloc.alloc(layout);
248            let ptr = alloc.realloc(ptr, layout, new_size);
249            alloc.dealloc(ptr, new_layout);
250        }
251    }
252}