#![allow(
missing_docs,
reason = "No need for API documentation in benchmark code"
)]
#![cfg_attr(
target_os = "linux",
expect(
clippy::exit,
clippy::missing_docs_in_private_items,
unused_qualifications,
reason = "These lints originate in Gungraun macro expansion and cannot be addressed in \
this benchmark."
)
)]
use alloc_tracker::Allocator;
#[global_allocator]
static ALLOCATOR: Allocator<std::alloc::System> = Allocator::system();
#[cfg(not(target_os = "linux"))]
fn main() {
}
#[cfg(target_os = "linux")]
use gungraun::{Callgrind, CallgrindMetrics, LibraryBenchmarkConfig, main};
#[cfg(target_os = "linux")]
pub use linux::*;
#[cfg(target_os = "linux")]
main!(
config = LibraryBenchmarkConfig::default().tool(
Callgrind::default()
.args(["--branch-sim=yes", "--collect-bus=yes"])
.format([CallgrindMetrics::Default, CallgrindMetrics::BranchSim]),
),
library_benchmark_groups = [allocator, overhead]
);
#[cfg(target_os = "linux")]
mod linux {
use std::alloc::{GlobalAlloc, Layout, handle_alloc_error};
use std::hint::black_box;
use std::sync::LazyLock;
use alloc_tracker::{Operation, Session};
use gungraun::prelude::*;
use crate::ALLOCATOR;
const SMALL_SIZE: usize = 64;
const LARGE_SIZE: usize = 64 * 1024;
const REALLOC_GROWN_SIZE: usize = SMALL_SIZE * 2;
fn layout(size: usize) -> Layout {
Layout::from_size_align(size, align_of::<u64>()).expect(
"a type's alignment is a non-zero power of two and the benchmark sizes are \
orders of magnitude below the layout size limit",
)
}
fn allocate<A: GlobalAlloc>(allocator: &A, layout: Layout) -> (*mut u8, Layout) {
let ptr = unsafe { allocator.alloc(layout) };
if ptr.is_null() {
handle_alloc_error(layout);
}
(ptr, layout)
}
fn warmed(size: usize) -> Layout {
let layout = layout(size);
alloc_dealloc(&std::alloc::System, layout);
layout
}
fn primed(size: usize) -> Layout {
let layout = layout(size);
alloc_dealloc(&ALLOCATOR, layout);
layout
}
fn growable<A: GlobalAlloc>(allocator: &A) -> ((*mut u8, Layout), Layout) {
(
allocate(allocator, layout(SMALL_SIZE)),
layout(REALLOC_GROWN_SIZE),
)
}
fn alloc_dealloc<A: GlobalAlloc>(allocator: &A, layout: Layout) {
let (ptr, layout) = allocate(allocator, layout);
black_box(ptr);
unsafe {
allocator.dealloc(ptr, layout);
}
}
fn realloc_grow<A: GlobalAlloc>(allocator: &A, block: (*mut u8, Layout), grown_layout: Layout) {
let (ptr, layout) = block;
let grown = unsafe { allocator.realloc(ptr, layout, grown_layout.size()) };
if grown.is_null() {
handle_alloc_error(grown_layout);
}
black_box(grown);
}
fn dealloc<A: GlobalAlloc>(allocator: &A, block: (*mut u8, Layout)) {
let (ptr, layout) = block;
unsafe {
allocator.dealloc(ptr, layout);
}
}
#[library_benchmark]
#[bench::run(warmed(SMALL_SIZE))]
fn allocator_untracked_alloc_dealloc_small(layout: Layout) {
alloc_dealloc(&std::alloc::System, black_box(layout));
}
#[library_benchmark]
#[bench::run(primed(SMALL_SIZE))]
fn allocator_tracked_alloc_dealloc_small(layout: Layout) {
alloc_dealloc(&ALLOCATOR, black_box(layout));
}
#[library_benchmark]
#[bench::run(warmed(LARGE_SIZE))]
fn allocator_untracked_alloc_dealloc_large(layout: Layout) {
alloc_dealloc(&std::alloc::System, black_box(layout));
}
#[library_benchmark]
#[bench::run(primed(LARGE_SIZE))]
fn allocator_tracked_alloc_dealloc_large(layout: Layout) {
alloc_dealloc(&ALLOCATOR, black_box(layout));
}
#[library_benchmark]
#[bench::run(allocate(&std::alloc::System, layout(SMALL_SIZE)))]
fn allocator_untracked_dealloc_small(block: (*mut u8, Layout)) {
dealloc(&std::alloc::System, black_box(block));
}
#[library_benchmark]
#[bench::run(allocate(&ALLOCATOR, layout(SMALL_SIZE)))]
fn allocator_tracked_dealloc_small(block: (*mut u8, Layout)) {
dealloc(&ALLOCATOR, black_box(block));
}
#[library_benchmark]
#[bench::run(growable(&std::alloc::System))]
fn allocator_untracked_realloc_grow(prepared: ((*mut u8, Layout), Layout)) {
let (block, grown_layout) = black_box(prepared);
realloc_grow(&std::alloc::System, block, grown_layout);
}
#[library_benchmark]
#[bench::run(growable(&ALLOCATOR))]
fn allocator_tracked_realloc_grow(prepared: ((*mut u8, Layout), Layout)) {
let (block, grown_layout) = black_box(prepared);
realloc_grow(&ALLOCATOR, block, grown_layout);
}
library_benchmark_group!(
name = allocator,
benchmarks = [
allocator_untracked_alloc_dealloc_small,
allocator_tracked_alloc_dealloc_small,
allocator_untracked_alloc_dealloc_large,
allocator_tracked_alloc_dealloc_large,
allocator_untracked_dealloc_small,
allocator_tracked_dealloc_small,
allocator_untracked_realloc_grow,
allocator_tracked_realloc_grow
]
);
struct Fixture {
thread_op: Operation,
process_op: Operation,
}
static FIXTURE: LazyLock<Fixture> = LazyLock::new(|| {
let session = Session::new().no_stdout().no_file();
Fixture {
thread_op: session.operation("thread_span_empty"),
process_op: session.operation("process_span_empty"),
}
});
fn fixture() -> &'static Fixture {
let fixture = &*FIXTURE;
drop(fixture.thread_op.measure_thread().iterations(1));
drop(fixture.process_op.measure_process().iterations(1));
fixture
}
#[library_benchmark]
#[bench::run(fixture())]
fn overhead_thread_span_empty(fixture: &'static Fixture) {
let _span = fixture.thread_op.measure_thread().iterations(1);
}
#[library_benchmark]
#[bench::run(fixture())]
fn overhead_process_span_empty(fixture: &'static Fixture) {
let _span = fixture.process_op.measure_process().iterations(1);
}
library_benchmark_group!(
name = overhead,
benchmarks = [overhead_thread_span_empty, overhead_process_span_empty]
);
}