codspeed_divan_compat_walltime/
alloc.rs1use std::{alloc::*, fmt, ptr::NonNull};
2
3use cfg_if::cfg_if;
4
5use crate::{stats::StatsSet, util::sync::AtomicFlag};
6
7#[cfg(target_os = "macos")]
8use crate::util::{sync::CachePadded, thread::PThreadKey};
9
10#[cfg(not(target_os = "macos"))]
11use std::cell::UnsafeCell;
12
13#[cfg(test)]
19#[global_allocator]
20static ALLOC: AllocProfiler = AllocProfiler::system();
21
22pub(crate) static IGNORE_ALLOC: AtomicFlag = AtomicFlag::new(false);
24
25#[derive(Debug, Default)]
124pub struct AllocProfiler<Alloc = System> {
125 alloc: Alloc,
126}
127
128unsafe impl<A: GlobalAlloc> GlobalAlloc for AllocProfiler<A> {
129 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
130 if let Some(mut info) = ThreadAllocInfo::try_current() {
132 let info = unsafe { info.as_mut() };
134
135 info.tally_alloc(layout.size());
136 };
137
138 self.alloc.alloc(layout)
139 }
140
141 unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
142 if let Some(mut info) = ThreadAllocInfo::try_current() {
144 let info = unsafe { info.as_mut() };
146
147 info.tally_alloc(layout.size());
148 };
149
150 self.alloc.alloc_zeroed(layout)
151 }
152
153 unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
154 if let Some(mut info) = ThreadAllocInfo::try_current() {
156 let info = unsafe { info.as_mut() };
158
159 info.tally_realloc(layout.size(), new_size);
160 };
161
162 self.alloc.realloc(ptr, layout, new_size)
163 }
164
165 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
166 if let Some(mut info) = ThreadAllocInfo::try_current() {
168 let info = unsafe { info.as_mut() };
170
171 info.tally_dealloc(layout.size());
172 };
173
174 self.alloc.dealloc(ptr, layout)
175 }
176}
177
178impl AllocProfiler {
179 #[inline]
181 pub const fn system() -> Self {
182 Self::new(System)
183 }
184}
185
186impl<A> AllocProfiler<A> {
187 #[inline]
189 pub const fn new(alloc: A) -> Self {
190 Self { alloc }
191 }
192}
193
194#[derive(Clone, Default)]
196#[repr(C)]
197pub(crate) struct ThreadAllocInfo {
198 pub tallies: ThreadAllocTallyMap,
201
202 pub current_count: ThreadAllocCountSigned,
208 pub max_count: ThreadAllocCountSigned,
209 pub current_size: ThreadAllocCountSigned,
210 pub max_size: ThreadAllocCountSigned,
211}
212
213#[cfg(not(target_os = "macos"))]
214thread_local! {
215 static CURRENT_THREAD_INFO: UnsafeCell<ThreadAllocInfo> = const {
219 UnsafeCell::new(ThreadAllocInfo::new())
220 };
221}
222
223#[cfg(target_os = "macos")]
224static ALLOC_PTHREAD_KEY: CachePadded<PThreadKey<ThreadAllocInfo>> = CachePadded(PThreadKey::new());
225
226impl ThreadAllocInfo {
227 #[inline]
228 pub const fn new() -> Self {
229 Self {
230 tallies: ThreadAllocTallyMap::new(),
231 max_count: 0,
232 current_count: 0,
233 max_size: 0,
234 current_size: 0,
235 }
236 }
237
238 #[inline]
244 pub fn current() -> Option<NonNull<Self>> {
245 cfg_if! {
246 if #[cfg(target_os = "macos")] {
247 return Self::try_current().or_else(slow_impl);
248 } else {
249 Self::try_current()
250 }
251 }
252
253 #[cfg(target_os = "macos")]
254 #[cold]
255 #[inline(never)]
256 fn slow_impl() -> Option<NonNull<ThreadAllocInfo>> {
257 unsafe {
258 let layout = Layout::new::<ThreadAllocInfo>();
259
260 let Some(info_alloc) = NonNull::new(unsafe { System.alloc_zeroed(layout) }) else {
261 handle_alloc_error(layout);
262 };
263
264 let success = ALLOC_PTHREAD_KEY.0.set(info_alloc.as_ptr().cast(), |this| {
265 System.dealloc(this.as_ptr().cast(), Layout::new::<ThreadAllocInfo>());
266 });
267
268 if !success {
269 System.dealloc(info_alloc.as_ptr(), layout);
270 return None;
271 }
272
273 #[cfg(all(not(miri), not(feature = "dyn_thread_local"), target_arch = "x86_64"))]
277 unsafe {
278 crate::util::thread::fast::set_static_thread_local(info_alloc.as_ptr());
279 };
280
281 Some(info_alloc.cast())
282 }
283 }
284 }
285
286 #[inline]
291 pub fn try_current() -> Option<NonNull<Self>> {
292 cfg_if! {
293 if #[cfg(target_os = "macos")] {
294 #[cfg(all(
296 not(miri),
297 not(feature = "dyn_thread_local"),
298 target_arch = "x86_64",
299 ))]
300 return NonNull::new(unsafe {
301 crate::util::thread::fast::get_static_thread_local::<Self>().cast_mut()
302 });
303
304 #[allow(unreachable_code)]
305 ALLOC_PTHREAD_KEY.0.get()
306 } else {
307 CURRENT_THREAD_INFO.try_with(|info| unsafe {
308 NonNull::new_unchecked(info.get())
309 }).ok()
310 }
311 }
312 }
313
314 pub fn clear(&mut self) {
316 *self = Self::new();
317 }
318
319 #[inline]
321 pub fn tally_alloc(&mut self, size: usize) {
322 self.tally_op(AllocOp::Alloc, size);
323
324 self.current_count += 1;
325 self.max_count = self.max_count.max(self.current_count);
326
327 self.current_size += size as ThreadAllocCountSigned;
328 self.max_size = self.max_size.max(self.current_size);
329 }
330
331 #[inline]
333 pub fn tally_dealloc(&mut self, size: usize) {
334 self.tally_op(AllocOp::Dealloc, size);
335
336 self.current_count -= 1;
337 self.current_size -= size as ThreadAllocCountSigned;
338 }
339
340 #[inline]
342 pub fn tally_realloc(&mut self, old_size: usize, new_size: usize) {
343 let (diff, is_shrink) = new_size.overflowing_sub(old_size);
344 let diff = diff as isize;
345 let abs_diff = diff.wrapping_abs() as usize;
346
347 self.tally_op(AllocOp::realloc(is_shrink), abs_diff);
348
349 self.current_size += diff as ThreadAllocCountSigned;
351 self.max_size = self.max_size.max(self.current_size);
352 }
353
354 #[inline]
356 fn tally_op(&mut self, op: AllocOp, size: usize) {
357 let tally = self.tallies.get_mut(op);
358 tally.count += 1;
359 tally.size += size as ThreadAllocCount;
360 }
361}
362
363#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
371#[repr(C, align(16))]
372pub(crate) struct AllocTally<Count> {
373 pub count: Count,
375
376 pub size: Count,
378}
379
380pub(crate) type ThreadAllocCount = condtype::num::Usize64;
381pub(crate) type ThreadAllocCountSigned = condtype::num::Isize64;
382
383pub(crate) type ThreadAllocTally = AllocTally<ThreadAllocCount>;
384
385pub(crate) type TotalAllocTally = AllocTally<u128>;
386
387impl AllocTally<StatsSet<f64>> {
388 pub fn is_zero(&self) -> bool {
389 self.count.is_zero() && self.size.is_zero()
390 }
391}
392
393impl<C> AllocTally<C> {
394 #[inline]
395 pub fn as_array(&self) -> &[C; 2] {
396 unsafe { &*(self as *const _ as *const _) }
399 }
400}
401
402#[derive(Clone, Copy, PartialEq, Eq)]
406pub(crate) enum AllocOp {
407 Grow,
408 Shrink,
409 Alloc,
410 Dealloc,
411}
412
413impl AllocOp {
414 pub const ALL: [Self; 4] = {
415 use AllocOp::*;
416
417 [Grow, Shrink, Alloc, Dealloc]
419 };
420
421 #[inline]
422 pub fn realloc(shrink: bool) -> Self {
423 if shrink {
425 Self::Shrink
426 } else {
427 Self::Grow
428 }
429 }
430
431 #[inline]
432 pub fn name(self) -> &'static str {
433 match self {
434 Self::Grow => "grow",
435 Self::Shrink => "shrink",
436 Self::Alloc => "alloc",
437 Self::Dealloc => "dealloc",
438 }
439 }
440
441 #[inline]
442 pub fn prefix(self) -> &'static str {
443 match self {
444 Self::Grow => "grow:",
445 Self::Shrink => "shrink:",
446 Self::Alloc => "alloc:",
447 Self::Dealloc => "dealloc:",
448 }
449 }
450}
451
452#[derive(Clone, Copy, Default, PartialEq, Eq)]
454pub(crate) struct AllocOpMap<T> {
455 pub values: [T; 4],
456}
457
458pub(crate) type ThreadAllocTallyMap = AllocOpMap<ThreadAllocTally>;
459
460pub(crate) type TotalAllocTallyMap = AllocOpMap<TotalAllocTally>;
461
462impl<T: fmt::Debug> fmt::Debug for AllocOpMap<T> {
463 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
464 f.debug_map().entries(AllocOp::ALL.iter().map(|&op| (op.name(), self.get(op)))).finish()
465 }
466}
467
468impl ThreadAllocTallyMap {
469 #[inline]
470 pub const fn new() -> Self {
471 unsafe { std::mem::transmute([0u8; size_of::<Self>()]) }
472 }
473
474 #[inline]
476 pub fn is_empty(&self) -> bool {
477 self.values.iter().all(|tally| tally.count == 0 && tally.size == 0)
478 }
479
480 pub fn add_to_total(&self, total: &mut TotalAllocTallyMap) {
481 for (i, value) in self.values.iter().enumerate() {
482 total.values[i].count += value.count as u128;
483 total.values[i].size += value.size as u128;
484 }
485 }
486}
487
488impl<T> AllocOpMap<T> {
489 #[cfg(test)]
490 pub fn from_fn<F>(f: F) -> Self
491 where
492 F: FnMut(AllocOp) -> T,
493 {
494 Self { values: AllocOp::ALL.map(f) }
495 }
496
497 #[inline]
498 pub const fn get(&self, op: AllocOp) -> &T {
499 &self.values[op as usize]
500 }
501
502 #[inline]
503 pub fn get_mut(&mut self, op: AllocOp) -> &mut T {
504 &mut self.values[op as usize]
505 }
506}
507
508#[cfg(feature = "internal_benches")]
509mod benches {
510 use super::*;
511
512 const THREADS: &[usize] = &[0, 1, 2, 4, 16];
514
515 #[crate::bench(crate = crate, threads = THREADS)]
516 fn tally_alloc(bencher: crate::Bencher) {
517 IGNORE_ALLOC.set(true);
518
519 let size = crate::black_box(0);
521
522 bencher.bench(|| {
523 if let Some(mut info) = ThreadAllocInfo::try_current() {
524 let info = unsafe { info.as_mut() };
526
527 info.tally_alloc(size);
528 }
529 })
530 }
531
532 #[crate::bench(crate = crate, threads = THREADS)]
533 fn tally_dealloc(bencher: crate::Bencher) {
534 IGNORE_ALLOC.set(true);
535
536 let size = crate::black_box(0);
538
539 bencher.bench(|| {
540 if let Some(mut info) = ThreadAllocInfo::try_current() {
541 let info = unsafe { info.as_mut() };
543
544 info.tally_dealloc(size);
545 }
546 })
547 }
548
549 #[crate::bench(crate = crate, threads = THREADS)]
550 fn tally_realloc(bencher: crate::Bencher) {
551 IGNORE_ALLOC.set(true);
552
553 let new_size = crate::black_box(0);
555 let old_size = crate::black_box(0);
556
557 bencher.bench(|| {
558 if let Some(mut info) = ThreadAllocInfo::try_current() {
559 let info = unsafe { info.as_mut() };
561
562 info.tally_realloc(old_size, new_size);
563 }
564 })
565 }
566
567 #[crate::bench_group(crate = crate, threads = THREADS)]
568 mod current {
569 use super::*;
570
571 #[crate::bench(crate = crate)]
572 fn init() -> Option<NonNull<ThreadAllocInfo>> {
573 ThreadAllocInfo::current()
574 }
575
576 #[crate::bench(crate = crate)]
577 fn r#try() -> Option<NonNull<ThreadAllocInfo>> {
578 ThreadAllocInfo::try_current()
579 }
580 }
581}
582
583#[cfg(test)]
584mod tests {
585 use super::*;
586
587 #[test]
589 fn tally() {
590 let mut alloc_info = ThreadAllocInfo::current().unwrap();
596
597 let mut take_alloc_tallies = || std::mem::take(unsafe { &mut alloc_info.as_mut().tallies });
599
600 _ = take_alloc_tallies();
602
603 let item_tally = ThreadAllocTally { count: 1, size: size_of::<i32>() as _ };
606 let make_tally_map = |op: AllocOp| {
607 ThreadAllocTallyMap::from_fn(|other_op| {
608 if other_op == op {
609 item_tally
610 } else {
611 Default::default()
612 }
613 })
614 };
615
616 let mut buf: Vec<i32> = Vec::new();
618 assert_eq!(take_alloc_tallies(), Default::default());
619
620 buf.reserve_exact(1);
622 assert_eq!(take_alloc_tallies(), make_tally_map(AllocOp::Alloc));
623
624 buf.reserve_exact(2);
626 assert_eq!(take_alloc_tallies(), make_tally_map(AllocOp::Grow));
627
628 buf.shrink_to(1);
630 assert_eq!(take_alloc_tallies(), make_tally_map(AllocOp::Shrink));
631
632 drop(buf);
634 assert_eq!(take_alloc_tallies(), make_tally_map(AllocOp::Dealloc));
635
636 let mut buf: Vec<i32> = Vec::new();
638 buf.reserve_exact(1); buf.reserve_exact(2); buf.shrink_to(1); drop(buf); assert_eq!(take_alloc_tallies(), ThreadAllocTallyMap { values: [item_tally; 4] });
643 }
644}