#![cfg(feature = "alloc-global")]
#![allow(
clippy::cast_possible_truncation,
clippy::needless_pass_by_value,
clippy::semicolon_if_nothing_returned
)]
use std::alloc::{GlobalAlloc, Layout, System};
use std::hint::black_box;
use std::time::Duration;
use criterion::{criterion_group, criterion_main, Criterion};
use sefer_alloc::SeferAlloc;
const SIZES: &[usize] = &[16, 64, 256, 1024];
const OPS: usize = 1024;
const CHURN_WORKING_SET: usize = 256;
struct XorShift64(u64);
impl XorShift64 {
const fn new(seed: u64) -> Self {
Self(seed | 1)
}
#[inline]
fn next_usize(&mut self) -> usize {
let mut x = self.0;
x ^= x >> 12;
x ^= x << 25;
x ^= x >> 27;
self.0 = x;
x.wrapping_mul(0x2545_F491_4F6C_DD1D) as usize
}
}
fn bench_direct_alloc<A: GlobalAlloc>(alloc: &A, layout: Layout) {
let mut ptrs: [*mut u8; OPS] = [core::ptr::null_mut(); OPS];
for slot in ptrs.iter_mut() {
*slot = unsafe { alloc.alloc(layout) };
}
black_box(&ptrs);
for &ptr in &ptrs {
if !ptr.is_null() {
unsafe { alloc.dealloc(ptr, layout) };
}
}
}
fn bench_churn_alloc<A: GlobalAlloc>(alloc: &A, layout: Layout, working_set: usize, ops: usize) {
let mut rng = XorShift64::new(0xCAFE);
let mut live: Vec<*mut u8> = Vec::with_capacity(working_set);
for _ in 0..working_set {
let p = unsafe { alloc.alloc(layout) };
live.push(p);
}
for _ in 0..ops {
let idx = rng.next_usize() % working_set;
let old = live[idx];
if !old.is_null() {
unsafe { alloc.dealloc(old, layout) };
}
live[idx] = unsafe { alloc.alloc(layout) };
}
black_box(&live);
for &p in &live {
if !p.is_null() {
unsafe { alloc.dealloc(p, layout) };
}
}
}
fn bench_global_alloc(c: &mut Criterion) {
let mut group = c.benchmark_group("global_alloc");
group.sample_size(10);
group.warm_up_time(Duration::from_millis(150));
group.measurement_time(Duration::from_millis(600));
let sefer = SeferAlloc::new();
let mi = mimalloc::MiMalloc;
let sys = System;
for &size in SIZES {
let layout = Layout::from_size_align(size, 8).unwrap();
group.bench_function(format!("SeferAlloc/{size}B"), |b| {
b.iter(|| bench_direct_alloc(&sefer, layout))
});
group.bench_function(format!("mimalloc/{size}B"), |b| {
b.iter(|| bench_direct_alloc(&mi, layout))
});
group.bench_function(format!("System/{size}B"), |b| {
b.iter(|| bench_direct_alloc(&sys, layout))
});
}
const VEC_PUSHES: usize = 512;
group.bench_function("Vec_push/SeferAlloc", |b| {
b.iter(|| {
let mut ptr: *mut i64 = core::ptr::null_mut();
let mut cap: usize = 0;
let mut len: usize = 0;
let layout = Layout::array::<i64>(VEC_PUSHES.max(1)).unwrap();
for i in 0..VEC_PUSHES {
if len == cap {
let new_cap = if cap == 0 { 4 } else { cap * 2 };
let new_layout = Layout::array::<i64>(new_cap.max(VEC_PUSHES)).unwrap();
let new_ptr = unsafe { sefer.alloc(new_layout) };
if !new_ptr.is_null() && !ptr.is_null() {
unsafe {
core::ptr::copy_nonoverlapping(ptr, new_ptr as *mut i64, len);
sefer.dealloc(ptr as *mut u8, layout);
}
}
ptr = new_ptr as *mut i64;
cap = new_cap.max(VEC_PUSHES);
}
if !ptr.is_null() {
unsafe { ptr.add(len).write(i as i64) };
}
len += 1;
}
black_box(ptr);
black_box(len);
if !ptr.is_null() {
let final_layout = Layout::array::<i64>(cap.max(1)).unwrap();
unsafe { sefer.dealloc(ptr as *mut u8, final_layout) };
}
})
});
group.bench_function("Vec_push/mimalloc", |b| {
b.iter(|| {
let mut ptr: *mut i64 = core::ptr::null_mut();
let mut cap: usize = 0;
let mut len: usize = 0;
let layout = Layout::array::<i64>(VEC_PUSHES.max(1)).unwrap();
for i in 0..VEC_PUSHES {
if len == cap {
let new_cap = if cap == 0 { 4 } else { cap * 2 };
let new_layout = Layout::array::<i64>(new_cap.max(VEC_PUSHES)).unwrap();
let new_ptr = unsafe { mi.alloc(new_layout) };
if !new_ptr.is_null() && !ptr.is_null() {
unsafe {
core::ptr::copy_nonoverlapping(ptr, new_ptr as *mut i64, len);
mi.dealloc(ptr as *mut u8, layout);
}
}
ptr = new_ptr as *mut i64;
cap = new_cap.max(VEC_PUSHES);
}
if !ptr.is_null() {
unsafe { ptr.add(len).write(i as i64) };
}
len += 1;
}
black_box(ptr);
black_box(len);
if !ptr.is_null() {
let final_layout = Layout::array::<i64>(cap.max(1)).unwrap();
unsafe { mi.dealloc(ptr as *mut u8, final_layout) };
}
})
});
group.bench_function("Vec_push/System", |b| {
b.iter(|| {
let mut ptr: *mut i64 = core::ptr::null_mut();
let mut cap: usize = 0;
let mut len: usize = 0;
for i in 0..VEC_PUSHES {
if len == cap {
let new_cap = if cap == 0 { 4 } else { cap * 2 };
let new_layout = Layout::array::<i64>(new_cap.max(VEC_PUSHES)).unwrap();
let new_ptr = unsafe { sys.alloc(new_layout) };
if !new_ptr.is_null() && !ptr.is_null() {
let old_layout = Layout::array::<i64>(cap.max(1)).unwrap();
unsafe {
core::ptr::copy_nonoverlapping(ptr, new_ptr as *mut i64, len);
sys.dealloc(ptr as *mut u8, old_layout);
}
}
ptr = new_ptr as *mut i64;
cap = new_cap.max(VEC_PUSHES);
}
if !ptr.is_null() {
unsafe { ptr.add(len).write(i as i64) };
}
len += 1;
}
black_box(ptr);
black_box(len);
if !ptr.is_null() {
let final_layout = Layout::array::<i64>(cap.max(1)).unwrap();
unsafe { sys.dealloc(ptr as *mut u8, final_layout) };
}
})
});
group.finish();
}
fn bench_global_alloc_churn(c: &mut Criterion) {
let mut group = c.benchmark_group("global_alloc_churn");
group.sample_size(10);
group.warm_up_time(Duration::from_millis(150));
group.measurement_time(Duration::from_millis(600));
let sefer = SeferAlloc::new();
let mi = mimalloc::MiMalloc;
let sys = System;
for &size in SIZES {
let layout = Layout::from_size_align(size, 8).unwrap();
group.bench_function(format!("SeferAlloc/{size}B"), |b| {
b.iter(|| bench_churn_alloc(&sefer, layout, CHURN_WORKING_SET, OPS))
});
group.bench_function(format!("mimalloc/{size}B"), |b| {
b.iter(|| bench_churn_alloc(&mi, layout, CHURN_WORKING_SET, OPS))
});
group.bench_function(format!("System/{size}B"), |b| {
b.iter(|| bench_churn_alloc(&sys, layout, CHURN_WORKING_SET, OPS))
});
}
group.finish();
}
criterion_group!(benches, bench_global_alloc, bench_global_alloc_churn);
criterion_main!(benches);