1mod counter;
2mod memory_footprint;
3mod memory_pressure;
4
5use std::{
6 alloc::{GlobalAlloc, Layout},
7 marker::PhantomData,
8 ops::{Add, AddAssign},
9};
10
11use self::counter::{add, flush, remove, update};
12
13#[derive(Default, Clone, Debug)]
14pub struct AllocationInfo {
15 pub allocations: usize,
16 pub deallocations: usize,
17 pub allocation_count: usize,
18 pub deallocation_count: usize,
19}
20
21impl AllocationInfo {
22 pub const ZERO: Self = Self {
23 allocations: 0,
24 deallocations: 0,
25 allocation_count: 0,
26 deallocation_count: 0,
27 };
28
29 pub fn is_empty(&self) -> bool {
30 self.allocations == 0
31 && self.deallocations == 0
32 && self.allocation_count == 0
33 && self.deallocation_count == 0
34 }
35
36 pub fn memory_usage(&self) -> usize {
37 self.allocations.saturating_sub(self.deallocations)
38 }
39}
40
41impl Add<Self> for AllocationInfo {
42 type Output = Self;
43
44 fn add(self, other: Self) -> Self {
45 Self {
46 allocations: self.allocations + other.allocations,
47 deallocations: self.deallocations + other.deallocations,
48 allocation_count: self.allocation_count + other.allocation_count,
49 deallocation_count: self.deallocation_count + other.deallocation_count,
50 }
51 }
52}
53
54impl AddAssign<Self> for AllocationInfo {
55 fn add_assign(&mut self, other: Self) {
56 self.allocations += other.allocations;
57 self.deallocations += other.deallocations;
58 self.allocation_count += other.allocation_count;
59 self.deallocation_count += other.deallocation_count;
60 }
61}
62
63#[derive(Default, Clone, Debug)]
64pub struct AllocationCounters {
65 pub allocations: usize,
66 pub deallocations: usize,
67 pub allocation_count: usize,
68 pub deallocation_count: usize,
69 _not_send: PhantomData<*mut ()>,
70}
71
72impl AllocationCounters {
73 const fn new() -> Self {
74 Self {
75 allocation_count: 0,
76 deallocation_count: 0,
77 allocations: 0,
78 deallocations: 0,
79 _not_send: PhantomData {},
80 }
81 }
82}
83
84pub struct TurboMalloc;
87
88impl TurboMalloc {
89 pub fn memory_usage() -> usize {
100 #[cfg(all(feature = "custom_allocator", not(target_family = "wasm"), not(miri)))]
101 {
102 let mut current_commit = 0usize;
106 unsafe {
108 libmimalloc_sys::mi_process_info(
109 std::ptr::null_mut(),
110 std::ptr::null_mut(),
111 std::ptr::null_mut(),
112 std::ptr::null_mut(),
113 std::ptr::null_mut(),
114 &mut current_commit,
115 std::ptr::null_mut(),
116 std::ptr::null_mut(),
117 );
118 }
119 current_commit
120 }
121 #[cfg(not(all(feature = "custom_allocator", not(target_family = "wasm"), not(miri))))]
122 {
123 self::counter::get()
124 }
125 }
126
127 pub fn thread_stop() {
130 flush();
131 }
132
133 pub fn thread_park() {
134 Self::collect(false);
135 }
136
137 pub fn collect(force: bool) {
143 #[cfg(all(feature = "custom_allocator", not(target_family = "wasm"), not(miri)))]
144 unsafe {
145 libmimalloc_sys::mi_collect(force);
146 }
147 #[cfg(not(all(feature = "custom_allocator", not(target_family = "wasm"), not(miri))))]
148 {
149 let _ = force;
150 }
151 }
152
153 pub fn allocation_counters() -> AllocationCounters {
154 self::counter::allocation_counters()
155 }
156
157 pub fn reset_allocation_counters(start: AllocationCounters) {
158 self::counter::reset_allocation_counters(start);
159 }
160
161 pub fn memory_pressure() -> Option<u8> {
175 memory_pressure::memory_pressure()
176 }
177
178 pub fn memory_footprint() -> Option<usize> {
188 memory_footprint::memory_footprint()
189 }
190}
191
192#[inline]
194fn base_alloc() -> &'static impl GlobalAlloc {
195 #[cfg(all(feature = "custom_allocator", not(target_family = "wasm"), not(miri)))]
196 return &mimalloc::MiMalloc;
197 #[cfg(not(all(feature = "custom_allocator", not(target_family = "wasm"), not(miri))))]
198 return &std::alloc::System;
199}
200
201#[allow(unused_variables)]
202unsafe fn base_alloc_size(ptr: *const u8, layout: Layout) -> usize {
203 #[cfg(all(feature = "custom_allocator", not(target_family = "wasm"), not(miri)))]
204 return unsafe { mimalloc::MiMalloc.usable_size(ptr) };
205 #[cfg(not(all(feature = "custom_allocator", not(target_family = "wasm"), not(miri))))]
206 return layout.size();
207}
208
209unsafe impl GlobalAlloc for TurboMalloc {
210 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
211 let ret = unsafe { base_alloc().alloc(layout) };
212 if !ret.is_null() {
213 let size = unsafe { base_alloc_size(ret, layout) };
214 add(size);
215 }
216 ret
217 }
218
219 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
220 let size = unsafe { base_alloc_size(ptr, layout) };
221 unsafe { base_alloc().dealloc(ptr, layout) };
222 remove(size);
223 }
224
225 unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
226 let ret = unsafe { base_alloc().alloc_zeroed(layout) };
227 if !ret.is_null() {
228 let size = unsafe { base_alloc_size(ret, layout) };
229 add(size);
230 }
231 ret
232 }
233
234 unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
235 let old_size = unsafe { base_alloc_size(ptr, layout) };
236 let ret = unsafe { base_alloc().realloc(ptr, layout, new_size) };
237 if !ret.is_null() {
238 let new_layout = unsafe { Layout::from_size_align_unchecked(new_size, layout.align()) };
241 let new_size = unsafe { base_alloc_size(ret, new_layout) };
242 update(old_size, new_size);
243 }
244 ret
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::TurboMalloc;
251
252 #[global_allocator]
256 static ALLOC: TurboMalloc = TurboMalloc;
257
258 #[test]
260 fn memory_usage_is_reported_and_tracks_a_large_allocation() {
261 let before = TurboMalloc::memory_usage();
262 assert!(before > 0, "a running process has live memory");
263
264 const SIZE: usize = 256 * 1024 * 1024;
267 let mut buffer = vec![0u8; SIZE];
268 for chunk in buffer.chunks_mut(4096) {
269 chunk[0] = 1;
270 }
271 std::hint::black_box(&buffer);
272
273 let after = TurboMalloc::memory_usage();
274 assert!(
275 after >= before + SIZE / 2,
276 "expected a rise of at least {} bytes, got {before} -> {after}",
277 SIZE / 2
278 );
279 drop(buffer);
280 }
281
282 #[test]
283 fn memory_pressure_is_in_range() {
284 let value = TurboMalloc::memory_pressure();
285
286 #[cfg(any(
288 all(target_os = "linux", not(target_family = "wasm"), not(miri)),
289 target_os = "macos",
290 windows,
291 ))]
292 let value = value.expect("memory_pressure() should return Some on this platform");
293
294 #[cfg(not(any(
296 all(target_os = "linux", not(target_family = "wasm"), not(miri)),
297 target_os = "macos",
298 windows,
299 )))]
300 let Some(value) = value else {
301 return;
302 };
303
304 assert!(
305 value <= 100,
306 "memory_pressure() returned {value}, expected a value in 0..=100"
307 );
308 }
309}