#![cfg(feature = "symbolize")]
mod common;
use std::alloc::{GlobalAlloc, Layout};
use std::hint::black_box;
use std::thread;
use stoolap_jemalloc::{Jemalloc, prof};
#[global_allocator]
static GLOBAL: Jemalloc = Jemalloc::new();
const ALLOC_OBJECTS: usize = 0;
const INUSE_OBJECTS: usize = 2;
const INUSE_BYTES: usize = 3;
fn totals(function: &str) -> [u64; 4] {
common::parse(&prof::dump_pprof().unwrap()).totals(function)
}
const ALLOC_BYTES: usize = 1;
fn near(got: u64, want: u64, percent: u64) -> bool {
got.abs_diff(want) * 100 <= want * percent
}
#[inline(never)]
fn page_aligned_bytes(n: usize) -> Vec<*mut u8> {
let layout = Layout::from_size_align(1, 4096).unwrap();
let mut ptrs = Vec::with_capacity(n);
for _ in 0..n {
ptrs.push(black_box(unsafe { GLOBAL.alloc(layout) }));
}
ptrs
}
#[inline(never)]
fn four_kib_blocks(n: usize) -> Vec<*mut u8> {
let layout = Layout::from_size_align(4096, 8).unwrap();
let mut ptrs = Vec::with_capacity(n);
let mut i = 0;
while i < n {
ptrs.push(black_box(unsafe { GLOBAL.alloc(layout) }));
i += 1;
}
ptrs
}
#[inline(never)]
fn huge_block() -> Vec<u8> {
black_box(vec![1u8; (1 << 20) + 1])
}
#[inline(never)]
fn one_block_after_a_raise() -> Vec<u8> {
black_box(vec![1u8; 4096])
}
#[inline(never)]
fn blocks_after_a_cut(n: usize) -> Vec<Vec<u8>> {
let mut blocks = Vec::with_capacity(n);
let mut i = 0;
while i < n {
blocks.push(black_box(vec![1u8; 4096]));
i += 1;
}
blocks
}
#[inline(never)]
fn short_thread_block() {
drop(black_box(vec![1u8; 64 << 10]));
}
trait First {
fn allocate(&self) -> Vec<u8>;
}
impl First for common::traits::Twice {
#[inline(never)]
fn allocate(&self) -> Vec<u8> {
black_box(vec![11u8; 6000])
}
}
#[inline(never)]
fn first_block_of_a_thread() -> Vec<u8> {
black_box(vec![7u8; 16 << 20])
}
#[test]
fn estimates_match_the_allocations() {
prof::set_sample_interval(4096);
prof::activate();
drop(black_box(vec![0u8; 2 << 20]));
let ptrs = page_aligned_bytes(10_000);
let objects = totals("page_aligned_bytes")[ALLOC_OBJECTS];
assert!(near(objects, 10_000, 5), "{objects} objects for 10000");
for p in ptrs {
unsafe { GLOBAL.dealloc(p, Layout::from_size_align(1, 4096).unwrap()) };
}
let ptrs = four_kib_blocks(100_000);
let live = totals("four_kib_blocks");
assert!(near(live[ALLOC_OBJECTS], 100_000, 3), "{live:?} for 100000");
assert!(near(live[INUSE_OBJECTS], 100_000, 3), "{live:?} for 100000");
for p in ptrs {
unsafe { GLOBAL.dealloc(p, Layout::from_size_align(4096, 8).unwrap()) };
}
assert_eq!(totals("four_kib_blocks")[INUSE_OBJECTS], 0);
prof::set_sample_interval(1);
let blocks: Vec<Vec<u8>> = (0..10)
.map(|_| thread::spawn(first_block_of_a_thread).join().unwrap())
.collect();
let live = totals("first_block_of_a_thread")[INUSE_BYTES];
assert!(live >= 160 << 20, "{live} live bytes for 160 MiB");
drop(blocks);
drop(black_box(vec![0u8; 1 << 20]));
let block = huge_block();
let live = totals("huge_block")[INUSE_BYTES];
assert!(
live > (1 << 20) + 1 && live <= (1 << 20) + (64 << 10) && live.is_multiple_of(4096),
"{live} live bytes for 1 MiB and a byte"
);
drop(block);
{
use common::traits::{Second, Twice};
drop(black_box(vec![0u8; 1 << 20]));
let kept = (First::allocate(&Twice), Second::allocate(&Twice));
let profile = common::parse(&prof::dump_pprof().unwrap());
let has_allocate_in = |file: &str| {
profile
.functions
.values()
.any(|f| f.name.contains("allocate") && f.file.replace('\\', "/").ends_with(file))
};
assert!(has_allocate_in("common/traits.rs"), "Second::allocate");
assert!(has_allocate_in("prof_accuracy.rs"), "First::allocate");
drop(kept);
}
prof::set_sample_interval(4096);
drop(black_box(vec![0u8; 1 << 20]));
prof::set_sample_interval(1 << 30);
let block = one_block_after_a_raise();
let counted = totals("one_block_after_a_raise");
assert!(
counted[ALLOC_OBJECTS] <= 2 && counted[ALLOC_BYTES] <= 3 * 4096,
"{counted:?}"
);
drop(block);
prof::set_sample_interval(usize::MAX / 4);
drop(black_box(vec![0u8; 1 << 20]));
prof::set_sample_interval(4096);
let blocks = blocks_after_a_cut(10_000);
let objects = totals("blocks_after_a_cut")[ALLOC_OBJECTS];
assert!(near(objects, 10_000, 5), "{objects} objects for 10000");
drop(blocks);
prof::set_sample_interval(1 << 20);
for _ in 0..2000 {
thread::spawn(short_thread_block).join().unwrap();
}
let bytes = totals("short_thread_block")[ALLOC_BYTES];
assert!(
near(bytes, 2000 << 16, 35),
"{bytes} bytes for 2000 × 64 KiB"
);
}