1use std::alloc::{GlobalAlloc, Layout, System};
18use std::cell::Cell;
19use std::fmt;
20use std::fmt::{Debug, Display};
21use std::mem;
22use std::panic::catch_unwind;
23use std::sync::atomic::AtomicUsize;
24use std::sync::atomic::Ordering::{AcqRel, Relaxed};
25use std::sync::{Arc, Mutex};
26
27use crossbeam_channel::{unbounded, Receiver, Sender};
28use once_cell::race::OnceBox;
29use once_cell::unsync::Lazy;
30
31#[derive(Default)]
32pub struct AccountingAlloc<A = System> {
34 thread_counters: OnceBox<ThreadCounters>,
35 allocator: A,
36}
37
38#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
39pub struct AllocStats {
41 pub all_time: AllTimeAllocStats,
43
44 pub since_last: IncrementalAllocStats,
46}
47
48#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
49pub struct AllTimeAllocStats {
51 pub alloc: usize,
53 pub dealloc: usize,
55 pub largest_alloc: usize,
57}
58
59#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
60pub struct IncrementalAllocStats {
63 pub alloc: usize,
65 pub dealloc: usize,
67 pub largest_alloc: usize,
69}
70
71#[derive(Debug)]
72struct ThreadCounters {
73 tx: Sender<Arc<ThreadCounter>>,
74 shared: Mutex<ThreadCountersShared>,
75}
76
77#[derive(Debug)]
78struct ThreadCountersShared {
79 rx: Receiver<Arc<ThreadCounter>>,
80 counters: Vec<Arc<ThreadCounter>>,
81 dead_alloc: usize,
82 dead_dealloc: usize,
83 all_time: AllTimeAllocStats,
84}
85
86#[derive(Debug, Default)]
87struct ThreadCounter {
88 alloc: AtomicUsize,
89 dealloc: AtomicUsize,
90 largest_alloc: AtomicUsize,
91}
92
93#[derive(Clone, Copy, Debug)]
94enum ThreadCounterState {
95 Uninitialized,
96 Initializing(AllTimeAllocStats),
97 Initialized,
98}
99
100impl AccountingAlloc<System> {
101 pub const fn new() -> Self {
103 Self::with_allocator(System)
104 }
105}
106
107impl<A> AccountingAlloc<A> {
108 pub const fn with_allocator(allocator: A) -> Self {
112 Self { thread_counters: OnceBox::new(), allocator }
113 }
114
115 pub fn count(&self) -> AllocStats {
117 let thread_counters = self.thread_counters.get_or_init(Default::default);
118 thread_counters.shared.lock().unwrap().count()
119 }
120
121 pub fn inc(&self, mut alloc: usize, mut dealloc: usize) {
123 use ThreadCounterState::{Initialized, Initializing, Uninitialized};
124
125 thread_local! {
126 static COUNTER: Lazy<Arc<ThreadCounter>> = Default::default();
127 static STATE: Cell<ThreadCounterState> = Cell::new(Uninitialized);
128 }
129
130 let thread_counters = &self.thread_counters;
131
132 let _ignore = catch_unwind(move || {
136 match STATE.try_with(|state| state.get())? {
137 Uninitialized => {
138 STATE.try_with(|state| state.set(Initializing(AllTimeAllocStats::default())))?;
140
141 let counter = COUNTER.try_with(|counter| Arc::clone(counter))?;
143
144 let mut largest_alloc = alloc;
146 if let Initializing(init_counter) = STATE.try_with(|state| state.replace(Initialized))? {
147 alloc += init_counter.alloc;
148 dealloc += init_counter.dealloc;
149 largest_alloc = largest_alloc.max(init_counter.largest_alloc);
150 }
151
152 counter.alloc.fetch_add(alloc, Relaxed);
153 counter.dealloc.fetch_add(dealloc, Relaxed);
154 counter.largest_alloc.fetch_max(largest_alloc, AcqRel);
155
156 let thread_counters = thread_counters.get_or_init(Default::default);
157 let _ignore = thread_counters.tx.send(counter);
158
159 Ok(())
160 }
161
162 Initializing(init_counts) => STATE.try_with(|state| {
163 state.set(Initializing(AllTimeAllocStats {
164 alloc: init_counts.alloc + alloc,
165 dealloc: init_counts.dealloc + dealloc,
166 largest_alloc: init_counts.largest_alloc.max(alloc),
167 }))
168 }),
169
170 Initialized => COUNTER.try_with(|counter| {
173 counter.alloc.fetch_add(alloc, Relaxed);
174 counter.dealloc.fetch_add(dealloc, Relaxed);
175 counter.largest_alloc.fetch_max(alloc, AcqRel);
176 }),
177 }
178 });
179 }
180}
181
182impl<A: Debug> Debug for AccountingAlloc<A> {
183 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184 f.debug_struct("AccountingAlloc")
185 .field("thread_counters", &self.thread_counters.get())
186 .field("allocator", &self.allocator)
187 .finish()
188 }
189}
190
191impl<A> Display for AccountingAlloc<A> {
194 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195 let thread_counters = self.thread_counters.get_or_init(Default::default);
196 let mut shared = thread_counters.shared.lock().unwrap();
197
198 let AllTimeAllocStats { alloc, dealloc, largest_alloc } = shared.count().all_time;
199
200 for (thread_idx, thread_counter) in shared.counters.iter().enumerate() {
201 let thread_alloc = thread_counter.alloc.load(Relaxed);
202 let thread_dealloc = thread_counter.dealloc.load(Relaxed);
203 writeln!(f, "Thread {thread_idx}: alloc {thread_alloc} dealloc {thread_dealloc}")?;
204 }
205 let total = alloc - dealloc;
206 writeln!(
207 f,
208 "Total: {total} (alloc {alloc} dealloc {dealloc} largest_alloc {largest_alloc})"
209 )
210 }
211}
212
213unsafe impl<A: GlobalAlloc> GlobalAlloc for AccountingAlloc<A> {
214 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
215 self.inc(layout.size(), 0);
216 self.allocator.alloc(layout)
217 }
218
219 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
220 self.inc(0, layout.size());
221 self.allocator.dealloc(ptr, layout);
222 }
223
224 unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
225 self.inc(layout.size(), 0);
226 self.allocator.alloc_zeroed(layout)
227 }
228
229 unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
230 self.inc(new_size, layout.size());
231 self.allocator.realloc(ptr, layout, new_size)
232 }
233}
234
235impl Default for ThreadCounters {
236 fn default() -> Self {
237 let (tx, rx) = unbounded();
238 Self {
239 tx,
240 shared: Mutex::new(ThreadCountersShared {
241 rx,
242 counters: Vec::with_capacity(64),
243 dead_alloc: Default::default(),
244 dead_dealloc: Default::default(),
245 all_time: Default::default(),
246 }),
247 }
248 }
249}
250
251impl ThreadCountersShared {
252 fn count(&mut self) -> AllocStats {
253 let mut alloc = 0;
254 let mut dealloc = 0;
255 let mut largest_alloc = 0;
256
257 self.counters.retain_mut(|counter| match Arc::get_mut(counter) {
258 Some(counter) => {
259 self.dead_alloc += *counter.alloc.get_mut();
260 self.dead_dealloc += *counter.dealloc.get_mut();
261 largest_alloc = largest_alloc.max(*counter.largest_alloc.get_mut());
262 false
263 }
264 None => {
265 alloc += counter.alloc.load(Relaxed);
266 dealloc += counter.dealloc.load(Relaxed);
267 largest_alloc = largest_alloc.max(counter.largest_alloc.swap(0, AcqRel));
268 true
269 }
270 });
271
272 for counter in self.rx.try_iter() {
273 match Arc::try_unwrap(counter) {
274 Ok(mut counter) => {
275 self.dead_alloc += *counter.alloc.get_mut();
276 self.dead_dealloc += *counter.dealloc.get_mut();
277 largest_alloc = largest_alloc.max(*counter.largest_alloc.get_mut());
278 }
279 Err(counter) => {
280 alloc += counter.alloc.load(Relaxed);
281 dealloc += counter.dealloc.load(Relaxed);
282 largest_alloc = largest_alloc.max(counter.largest_alloc.swap(0, AcqRel));
283 self.counters.push(counter);
284 }
285 }
286 }
287
288 alloc += self.dead_alloc;
289 dealloc += self.dead_dealloc;
290
291 let all_time =
292 AllTimeAllocStats { alloc, dealloc, largest_alloc: self.all_time.largest_alloc.max(largest_alloc) };
293 let last_all_time = mem::replace(&mut self.all_time, all_time);
294 let since_last = IncrementalAllocStats {
295 alloc: alloc - last_all_time.alloc,
296 dealloc: dealloc - last_all_time.dealloc,
297 largest_alloc,
298 };
299
300 AllocStats { all_time, since_last }
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use std::convert::identity;
307
308 use crossbeam_utils::thread::scope;
309
310 use super::*;
311
312 #[derive(Default)]
313 struct TestAlloc;
323
324 struct Allocation {
326 layout: Layout,
327 }
328
329 struct AllocationHandle<'a> {
333 allocator: &'a AccountingAlloc<TestAlloc>,
334 ptr: *mut u8,
335 layout: Layout,
336 }
337
338 fn test_allocations<'a, T>(
344 allocator: &'a AccountingAlloc<TestAlloc>,
345 allocate: fn(&'a AccountingAlloc<TestAlloc>, Layout) -> AllocationHandle<'a>,
346 callback: impl FnOnce(Vec<AllocationHandle<'a>>) -> T,
347 ) -> T {
348 let layouts: Vec<_> = (1..10).map(|idx| Layout::array::<u8>(10000 * idx).unwrap()).collect();
349 let (allocations_tx, allocations_rx) = unbounded();
350 scope(|scope| {
351 for layout in layouts.clone() {
352 let allocations_tx = allocations_tx.clone();
353 scope.spawn(move |_scope| allocations_tx.send(allocate(allocator, layout)).unwrap());
354 }
355 drop(allocations_tx);
356 callback(allocations_rx.into_iter().collect())
357 })
358 .unwrap()
359 }
360
361 fn expected_counts<'a>(allocations: &[AllocationHandle<'a>]) -> AllocStats {
364 let allocation_sizes = allocations.iter().map(|allocation| allocation.layout.size());
365 let since_last = IncrementalAllocStats {
366 alloc: allocation_sizes.clone().sum::<usize>(),
367 dealloc: 0,
368 largest_alloc: allocation_sizes.max().unwrap(),
369 };
370 AllocStats {
371 all_time: AllTimeAllocStats {
372 alloc: since_last.alloc,
373 dealloc: since_last.dealloc,
374 largest_alloc: since_last.largest_alloc,
375 },
376 since_last,
377 }
378 }
379
380 #[test]
381 fn alloc() {
382 let allocator = Default::default();
383 let (_allocations, expected) = test_allocations(&allocator, AllocationHandle::new, |allocations| {
384 let expected = expected_counts(&allocations);
386 assert_eq!(allocator.count(), expected);
387 (allocations, expected)
388 });
389
390 assert_eq!(
392 allocator.count(),
393 AllocStats { since_last: Default::default(), ..expected }
394 );
395 }
396
397 #[test]
398 fn dealloc() {
399 let allocator = &Default::default();
400 let allocations = test_allocations(&allocator, AllocationHandle::new, identity);
401 let expected = expected_counts(&allocations);
402
403 assert_eq!(allocator.count(), expected);
404
405 scope(|scope| {
406 for allocation in allocations {
407 scope.spawn(move |_scope| drop(allocation));
408 }
409 })
410 .unwrap();
411
412 assert_eq!(
413 allocator.count(),
414 AllocStats {
415 all_time: AllTimeAllocStats { dealloc: expected.all_time.alloc, ..expected.all_time },
416 since_last: IncrementalAllocStats { dealloc: expected.all_time.alloc, ..Default::default() },
417 }
418 );
419 }
420
421 #[test]
422 fn alloc_zeroed() {
423 let allocator = &Default::default();
424 let allocations = test_allocations(&allocator, AllocationHandle::new_zeroed, identity);
425 let expected = expected_counts(&allocations);
426
427 assert_eq!(allocator.count(), expected);
428 }
429
430 #[test]
431 fn realloc() {
432 let allocator = &Default::default();
433 let mut allocations = test_allocations(&allocator, AllocationHandle::new, identity);
434 let expected = expected_counts(&allocations);
435
436 assert_eq!(allocator.count(), expected);
437
438 scope(|scope| {
439 for allocation in &mut allocations {
440 scope.spawn(move |_scope| allocation.realloc(allocation.layout.size() * 2));
441 }
442 })
443 .unwrap();
444
445 let expected_2 = expected_counts(&allocations);
446
447 assert_eq!(
448 allocator.count(),
449 AllocStats {
450 all_time: AllTimeAllocStats {
451 alloc: expected.since_last.alloc + expected_2.since_last.alloc,
452 dealloc: expected.since_last.alloc,
453 largest_alloc: expected_2.since_last.largest_alloc,
454 },
455 since_last: IncrementalAllocStats { dealloc: expected.since_last.alloc, ..expected_2.since_last }
456 }
457 );
458 }
459
460 unsafe impl GlobalAlloc for TestAlloc {
461 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
462 Box::into_raw(Box::new(Allocation { layout })) as *mut u8
467 }
468
469 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
470 let allocation = Box::from_raw(ptr as *mut Allocation);
472 assert_eq!(layout, allocation.layout);
473 drop(allocation);
474 }
475
476 unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
477 self.alloc(layout)
478 }
479
480 unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
481 self.dealloc(ptr, layout);
482 self.alloc(Layout::from_size_align_unchecked(new_size, layout.align()))
483 }
484 }
485
486 impl<'a> AllocationHandle<'a> {
487 fn new(allocator: &'a AccountingAlloc<TestAlloc>, layout: Layout) -> Self {
489 Self { allocator, ptr: unsafe { allocator.alloc(layout) }, layout }
490 }
491
492 fn new_zeroed(allocator: &'a AccountingAlloc<TestAlloc>, layout: Layout) -> Self {
494 Self { allocator, ptr: unsafe { allocator.alloc_zeroed(layout) }, layout }
495 }
496
497 fn realloc(&mut self, new_size: usize) {
499 unsafe {
500 self.ptr = self.allocator.realloc(self.ptr, self.layout, new_size);
501 self.layout = Layout::from_size_align_unchecked(new_size, self.layout.align());
502 }
503 }
504 }
505
506 unsafe impl Send for AllocationHandle<'_> {}
507
508 impl Drop for AllocationHandle<'_> {
509 fn drop(&mut self) {
510 unsafe { self.allocator.dealloc(self.ptr, self.layout) };
511 }
512 }
513}