Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions libdd-profiling-heap-allocator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ libdd-profiling-heap-sampler = { version = "1.0.0", path = "../libdd-profiling-h
[dev-dependencies]
criterion.workspace = true

[target.'cfg(target_os = "linux")'.dev-dependencies]
libc.workspace = true

[[bench]]
name = "sampler_overhead"
harness = false
109 changes: 73 additions & 36 deletions libdd-profiling-heap-allocator/benches/sampler_overhead.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ mod linux_bench {
use libdd_profiling_heap_sampler::{dd_test_set_profiler_active, dd_tl_state_get_or_init};
use std::alloc::{GlobalAlloc, Layout, System};
use std::hint::black_box;
use std::mem::MaybeUninit;
use std::ptr;
use std::time::Duration;

const SIZES: &[usize] = &[16, 64, 256, 4096, 65_536];
const ALIGN: usize = 8;
Expand All @@ -28,6 +30,30 @@ mod linux_bench {

static mut NOOP_BUFFER: AlignedBuffer = AlignedBuffer([0; 128 * 1024]);

// Do not attribute time when this benchmark thread is descheduled to the sampler.
fn thread_cpu_time() -> Duration {
let mut time = MaybeUninit::<libc::timespec>::uninit();
let result =
unsafe { libc::clock_gettime(libc::CLOCK_THREAD_CPUTIME_ID, time.as_mut_ptr()) };
assert_eq!(
result,
0,
"read thread CPU time: {}",
std::io::Error::last_os_error()
);
let time = unsafe { time.assume_init() };
Duration::new(time.tv_sec as u64, time.tv_nsec as u32)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Justify or replace the timespec numeric casts

The new clock conversion silently casts libc's signed, platform-dependent time_t and c_long fields into narrower/unsigned Rust types. Although a valid CLOCK_THREAD_CPUTIME_ID result should be nonnegative with subsecond nanoseconds, encode those assumptions with checked conversions or document why the casts are safe; otherwise malformed or out-of-range values wrap or truncate before reaching Duration. The repository explicitly requires fallible conversions or a short justification for numeric as casts.

AGENTS.md reference: AGENTS.md:L74-L74

Useful? React with 👍 / 👎.

}

#[inline]
fn measure_thread_cpu_time(iterations: u64, mut routine: impl FnMut()) -> Duration {
let start = thread_cpu_time();
for _ in 0..iterations {
routine();
}
thread_cpu_time() - start
}

struct NoopAllocator;

unsafe impl GlobalAlloc for NoopAllocator {
Expand Down Expand Up @@ -91,14 +117,16 @@ mod linux_bench {
// Pure system allocator cost with no sampler in the picture.

fn bench_system_alloc_free(c: &mut Criterion) {
let mut group = c.benchmark_group("alloc_free/system");
let mut group = c.benchmark_group("thread_cpu/alloc_free/system");
for &size in SIZES {
let layout = Layout::from_size_align(size, ALIGN).unwrap();
group.bench_with_input(BenchmarkId::from_parameter(size), &layout, |b, &layout| {
b.iter(|| unsafe {
let ptr = System.alloc(layout);
black_box(ptr);
System.dealloc(ptr, layout);
b.iter_custom(|iterations| {
measure_thread_cpu_time(iterations, || unsafe {
let ptr = System.alloc(layout);
black_box(ptr);
System.dealloc(ptr, layout);
})
});
});
}
Expand All @@ -119,16 +147,18 @@ mod linux_bench {

fn bench_fast_path_system(c: &mut Criterion) {
let alloc = SampledAllocator::new(System);
let mut group = c.benchmark_group("profiler_attached/fast_path_system");
let mut group = c.benchmark_group("thread_cpu/profiler_attached/fast_path_system");
unsafe { dd_test_set_profiler_active(true) };
for &size in SIZES {
let layout = Layout::from_size_align(size, ALIGN).unwrap();
group.bench_with_input(BenchmarkId::from_parameter(size), &layout, |b, &layout| {
unsafe { pin_sampler_to_fast_path() };
b.iter(|| unsafe {
let ptr = alloc.alloc(layout);
black_box(ptr);
alloc.dealloc(ptr, layout);
b.iter_custom(|iterations| {
measure_thread_cpu_time(iterations, || unsafe {
let ptr = alloc.alloc(layout);
black_box(ptr);
alloc.dealloc(ptr, layout);
})
});
});
}
Expand All @@ -138,16 +168,18 @@ mod linux_bench {

fn bench_fast_path_noop(c: &mut Criterion) {
let alloc = SampledAllocator::new(NoopAllocator);
let mut group = c.benchmark_group("profiler_attached/fast_path_noop");
let mut group = c.benchmark_group("thread_cpu/profiler_attached/fast_path_noop");
unsafe { dd_test_set_profiler_active(true) };
for &size in SIZES {
let layout = Layout::from_size_align(size, ALIGN).unwrap();
group.bench_with_input(BenchmarkId::from_parameter(size), &layout, |b, &layout| {
unsafe { pin_sampler_to_fast_path() };
b.iter(|| unsafe {
let ptr = alloc.alloc(layout);
black_box(ptr);
alloc.dealloc(ptr, layout);
b.iter_custom(|iterations| {
measure_thread_cpu_time(iterations, || unsafe {
let ptr = alloc.alloc(layout);
black_box(ptr);
alloc.dealloc(ptr, layout);
})
});
});
}
Expand All @@ -157,16 +189,18 @@ mod linux_bench {

fn bench_slow_path_system(c: &mut Criterion) {
let alloc = SampledAllocator::new(System);
let mut group = c.benchmark_group("profiler_attached/slow_path_system");
let mut group = c.benchmark_group("thread_cpu/profiler_attached/slow_path_system");
unsafe { dd_test_set_profiler_active(true) };
for &size in SIZES {
let layout = Layout::from_size_align(size, ALIGN).unwrap();
group.bench_with_input(BenchmarkId::from_parameter(size), &layout, |b, &layout| {
b.iter(|| unsafe {
force_next_allocation_to_sample();
let ptr = alloc.alloc(layout);
black_box(ptr);
alloc.dealloc(ptr, layout);
b.iter_custom(|iterations| {
measure_thread_cpu_time(iterations, || unsafe {
force_next_allocation_to_sample();
let ptr = alloc.alloc(layout);
black_box(ptr);
alloc.dealloc(ptr, layout);
})
});
});
}
Expand All @@ -176,16 +210,18 @@ mod linux_bench {

fn bench_slow_path_noop(c: &mut Criterion) {
let alloc = SampledAllocator::new(NoopAllocator);
let mut group = c.benchmark_group("profiler_attached/slow_path_noop");
let mut group = c.benchmark_group("thread_cpu/profiler_attached/slow_path_noop");
unsafe { dd_test_set_profiler_active(true) };
for &size in SIZES {
let layout = Layout::from_size_align(size, ALIGN).unwrap();
group.bench_with_input(BenchmarkId::from_parameter(size), &layout, |b, &layout| {
b.iter(|| unsafe {
force_next_allocation_to_sample();
let ptr = alloc.alloc(layout);
black_box(ptr);
alloc.dealloc(ptr, layout);
b.iter_custom(|iterations| {
measure_thread_cpu_time(iterations, || unsafe {
force_next_allocation_to_sample();
let ptr = alloc.alloc(layout);
black_box(ptr);
alloc.dealloc(ptr, layout);
})
});
});
}
Expand All @@ -195,21 +231,22 @@ mod linux_bench {

// ── Short-circuit regression (semaphore OFF) ─────────────────────────
// Single benchmark with the semaphore off (no profiler attached).
// The semaphore check in dd_allocation_requested short-circuits before
// any TLS access or sampling logic. This validates that the
// short-circuit path stays near-zero cost.
// The no-op allocator keeps the system allocator from obscuring the
// semaphore check this benchmark is intended to validate.

fn bench_short_circuit(c: &mut Criterion) {
let alloc = SampledAllocator::new(System);
let mut group = c.benchmark_group("no_profiler/short_circuit");
let alloc = SampledAllocator::new(NoopAllocator);
let mut group = c.benchmark_group("thread_cpu/no_profiler/short_circuit_noop");
// Semaphore is off by default - don't flip it on.
for &size in SIZES {
let layout = Layout::from_size_align(size, ALIGN).unwrap();
group.bench_with_input(BenchmarkId::from_parameter(size), &layout, |b, &layout| {
b.iter(|| unsafe {
let ptr = alloc.alloc(layout);
black_box(ptr);
alloc.dealloc(ptr, layout);
b.iter_custom(|iterations| {
measure_thread_cpu_time(iterations, || unsafe {
let ptr = alloc.alloc(layout);
black_box(ptr);
alloc.dealloc(ptr, layout);
})
});
});
}
Expand Down
Loading