Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
12dcae3
allocator trait
pzittlau Jan 9, 2025
e5cb35f
first fit
pzittlau Jan 9, 2025
b6acfa1
next fit
pzittlau Jan 9, 2025
f9733fe
best fit simple
pzittlau Jan 9, 2025
82b947b
worst fit simple
pzittlau Jan 9, 2025
a1389d8
better allocator benchmarks
pzittlau Jan 9, 2025
6a1c5f6
new benchmark grouping for joint plots
pzittlau Jan 15, 2025
ecbff0c
remove unused default
pzittlau Jan 15, 2025
d383626
first fit with list building on creation
pzittlau Jan 24, 2025
e3dee21
next fit with list building on creation
pzittlau Jan 24, 2025
6855386
benchmark with sync simulation
pzittlau Jan 24, 2025
971c28d
{first,next}_fit_list optimization
pzittlau Jan 24, 2025
2876ba8
{best,worst}_fit_list, renamed bitmap scan allocators
pzittlau Jan 24, 2025
02335d8
generic benchmark implementation for easier adding of allocators
pzittlau Jan 24, 2025
eb8918c
first fit fsm allocator
pzittlau Jan 24, 2025
aede2de
remove deallocation from Allocator trait
pzittlau Jan 24, 2025
a4f2652
best fit fsm allocator
pzittlau Jan 24, 2025
e0abd31
worst fit fsm allocator
pzittlau Jan 24, 2025
aac3a74
rename fsm to tree
pzittlau Jan 24, 2025
129da33
real best fit tree allocator
pzittlau Jan 26, 2025
c3aaf8f
fix index error in clock_cache
pzittlau Feb 17, 2025
ad9f350
linting
pzittlau Feb 17, 2025
cf76b2e
use build_threaded in c_interface
pzittlau Feb 17, 2025
74efcea
remove not improving optimizations, see branch fsm_optimization
pzittlau Feb 17, 2025
544b81d
new plots
pzittlau Feb 21, 2025
3b4a02f
hybrid allocator
pzittlau Feb 26, 2025
e91a3c0
small refactor
pzittlau Mar 3, 2025
a04ee99
support multiple pools
pzittlau Mar 3, 2025
a800ec7
proper allocate_at
pzittlau Mar 5, 2025
0b95217
rework allocator micro benchmarks
pzittlau Mar 7, 2025
ebff5ac
fix small bug
pzittlau Mar 7, 2025
e04cbaa
prepare benching of different segment sizes
pzittlau Mar 7, 2025
8bd149c
scripts and small tweaks for benching allocators
pzittlau Mar 7, 2025
ec2b929
preallocate vector in benchmark
pzittlau Mar 24, 2025
24c4033
state of bachelorsthesis
pzittlau May 8, 2025
b7b64a7
pr state
pzittlau Aug 20, 2025
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 betree/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ quickcheck = "1"
quickcheck_macros = "1"
clap = "2.33"
criterion = "0.3"
zipf = "7.0.1"

[features]
default = ["init_env_logger", "figment_config"]
Expand Down
281 changes: 274 additions & 7 deletions betree/benches/allocator.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,283 @@
use betree_storage_stack::allocator::{SegmentAllocator, SEGMENT_SIZE_BYTES};
use std::time::{Duration, Instant};

use betree_storage_stack::allocator::{
self, Allocator, BestFitFSM, BestFitList, BestFitScan, BestFitTree, FirstFitFSM, FirstFitList,
FirstFitScan, HybridAllocator, NextFitList, NextFitScan, SegmentAllocator, WorstFitFSM,
WorstFitList, WorstFitScan, SEGMENT_SIZE_BYTES, SEGMENT_SIZE_LOG_2,
};
use criterion::{black_box, criterion_group, criterion_main, Bencher, Criterion};
use rand::{
distributions::{Distribution, Uniform},
rngs::StdRng,
SeedableRng,
};
use zipf::ZipfDistribution;

#[derive(Clone)]
enum SizeDistribution {
Uniform(Uniform<usize>),
Zipfian(ZipfDistribution),
}

// Define a type alias for our benchmark function to make it less verbose
type BenchmarkFn = Box<dyn Fn(&mut Bencher, SizeDistribution, u64, u64, usize, usize)>;

// Macro to generate allocator benchmark entries
macro_rules! allocator_benchmark {
($name:expr, $allocator_type:ty, $bench_function:ident) => {
(
$name,
Box::new(|b, dist, allocations, deallocations, min_size, max_size| {
$bench_function::<$allocator_type>(
b,
dist,
allocations,
deallocations,
min_size,
max_size,
)
}),
)
};
}

// Macro to generate allocator benchmark entries with name derived from type
macro_rules! generate_allocator_benchmarks {
($bench_function:ident, $($allocator_type:ty),*) => {
vec![
$(
allocator_benchmark!(stringify!($allocator_type), $allocator_type, $bench_function),
)*
]
};
}

// In Haura, allocators are not continuously active in memory. Instead, they are loaded from disk
// when needed. This benchmark simulates this behavior by creating a new allocator instance for each
// iteration. Also deallocations are buffered and applied during sync operations, not immediately to
// the allocator. Here, we simulate the sync operation by directly modifying the underlying bitmap
// data after the allocator has performed allocations, mimicking the delayed deallocation process.
fn bench_alloc<A: Allocator>(
b: &mut Bencher,
dist: SizeDistribution,
allocations: u64,
deallocations: u64,
min_size: usize,
max_size: usize,
) {
let data = [0; SEGMENT_SIZE_BYTES];
let mut allocated = Vec::new();
allocated.reserve(allocations as usize);

let mut rng = StdRng::seed_from_u64(42);
let mut sample_size = || -> u32 {
match &dist {
SizeDistribution::Uniform(u) => return black_box(u.sample(&mut rng)) as u32,
SizeDistribution::Zipfian(z) => {
let rank = black_box(z.sample(&mut rng)) as usize;
return (min_size + (rank - 1)) as u32;
}
}
};

b.iter_custom(|iters| {
let mut total_allocation_time = Duration::new(0, 0);

for _ in 0..iters {
allocated.clear();
let mut allocator = A::new(data);

let start = Instant::now();
for _ in 0..allocations {
let size = sample_size();
if let Some(offset) = black_box(allocator.allocate(size)) {
allocated.push((offset, size));
}
}
total_allocation_time += start.elapsed();

// Simulates the deferred deallocations
let bitmap = allocator.data();
for _ in 0..deallocations {
if allocated.is_empty() {
break;
}
let idx = rand::random::<usize>() % allocated.len();
let (offset, size) = allocated.swap_remove(idx);

let start = offset as usize;
let end = (offset + size) as usize;
let range = &mut bitmap[start..end];
range.fill(false);
}
// At the end of the iteration, the allocator goes out of scope, simulating it being
// unloaded from memory. In the next iteration, a new allocator will be created and loaded
// with the modified bitmap data.
}
Duration::from_nanos((total_allocation_time.as_nanos() / allocations as u128) as u64)
});
}

fn bench_new<A: Allocator>(
b: &mut Bencher,
dist: SizeDistribution,
allocations: u64,
deallocations: u64,
min_size: usize,
max_size: usize,
) {
let data = [0; SEGMENT_SIZE_BYTES];
let mut allocated = Vec::new();
allocated.reserve(allocations as usize);

let mut rng = StdRng::seed_from_u64(42);
let mut sample_size = || -> u32 {
match &dist {
SizeDistribution::Uniform(u) => return black_box(u.sample(&mut rng)) as u32,
SizeDistribution::Zipfian(z) => {
let rank = black_box(z.sample(&mut rng)) as usize;
return (min_size + (rank - 1)) as u32; // Linear mapping for Zipfian
}
}
};

b.iter_custom(|iters| {
let mut total_allocation_time = Duration::new(0, 0);

for _ in 0..iters {
allocated.clear();
let start = Instant::now();
let mut allocator = A::new(data);
total_allocation_time += start.elapsed();

for _ in 0..allocations {
let size = sample_size();
if let Some(offset) = black_box(allocator.allocate(size)) {
allocated.push((offset, size));
}
}

// Simulates the deferred deallocations
let bitmap = allocator.data();
for _ in 0..deallocations {
if allocated.is_empty() {
break;
}
let idx = rand::random::<usize>() % allocated.len();
let (offset, size) = allocated.swap_remove(idx);

fn allocate(b: &mut Bencher) {
let mut a = SegmentAllocator::new([0; SEGMENT_SIZE_BYTES]);
b.iter(|| {
black_box(a.allocate(10));
let start = offset as usize;
let end = (offset + size) as usize;
let range = &mut bitmap[start..end];
range.fill(false);
}
// At the end of the iteration, the allocator goes out of scope, simulating it being
// unloaded from memory. In the next iteration, a new allocator will be created and loaded
// with the modified bitmap data.
}
total_allocation_time
});
}

pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("allocate", allocate);
let min_size = 128;
let max_size = 1024;
let zipfian_exponent = 0.99;

let distributions = [
(
"uniform",
SizeDistribution::Uniform(Uniform::new(min_size, max_size + 1)),
),
(
"zipfian",
SizeDistribution::Zipfian(
ZipfDistribution::new(max_size - min_size + 1, zipfian_exponent).expect(""),
),
),
];

let allocations = 2_u64.pow(SEGMENT_SIZE_LOG_2 as u32 - 10);
let deallocations = allocations / 2;

// Define the allocators to benchmark for allocation
#[rustfmt::skip]
let allocator_benchmarks_alloc: Vec<(&'static str, BenchmarkFn)> = generate_allocator_benchmarks!(
bench_alloc,
FirstFitScan,
FirstFitList,
FirstFitFSM,
NextFitScan,
NextFitList,
BestFitScan,
BestFitList,
BestFitFSM,
BestFitTree,
WorstFitScan,
WorstFitList,
WorstFitFSM,
SegmentAllocator
);

for (dist_name, dist) in distributions.clone() {
let group_name = format!("allocator_alloc_{}_{}", dist_name, SEGMENT_SIZE_LOG_2);
let mut group = c.benchmark_group(group_name);
for (bench_name, bench_func) in &allocator_benchmarks_alloc {
group.bench_function(*bench_name, |b| {
bench_func(
b,
dist.clone(),
allocations,
deallocations,
min_size,
max_size,
)
});
}
group.finish();
}

// Define the allocators to benchmark for 'new' function time
let allocator_benchmarks_new: Vec<(&'static str, BenchmarkFn)> = generate_allocator_benchmarks!(
bench_new,
FirstFitScan,
FirstFitList,
FirstFitFSM,
NextFitScan,
NextFitList,
BestFitScan,
BestFitList,
BestFitFSM,
BestFitTree,
WorstFitScan,
WorstFitList,
WorstFitFSM,
SegmentAllocator
);

for (dist_name, dist) in distributions.clone() {
let group_name = format!("allocator_new_{}_{}", dist_name, SEGMENT_SIZE_LOG_2);
let mut group = c.benchmark_group(group_name);
for (bench_name, bench_func) in &allocator_benchmarks_new {
group.bench_function(*bench_name, |b| {
bench_func(
b,
dist.clone(),
allocations,
deallocations,
min_size,
max_size,
)
});
}
group.finish();
}
}

criterion_group!(benches, criterion_benchmark);
criterion_group! {
name = benches;
// This can be any expression that returns a `Criterion` object.
config = Criterion::default().sample_size(500).measurement_time(Duration::new(600, 0)).warm_up_time(Duration::new(10, 0));
targets = criterion_benchmark
}
criterion_main!(benches);
Loading