use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::thread;
use std::time::Instant;
use criterion::{
BenchmarkId, Criterion, Throughput, black_box, criterion_group, criterion_main,
};
use polydat::ast::Value;
use polydat::compile::assembly::{PolydatAssembler, WireRef};
use polydat::kernel::{PolydatKernel, SharedCell, SharedCellInner};
use polydat::library::arithmetic::Sum;
fn standalone_cell(initial: Value) -> SharedCell {
let intent = Arc::new(AtomicU64::new(0));
Arc::new(SharedCellInner::new(initial, intent, 0))
}
fn cell_in_scope(intent_word: Arc<AtomicU64>, bit: u8, initial: Value) -> SharedCell {
Arc::new(SharedCellInner::new(initial, intent_word, bit))
}
fn build_n_input_kernel(n_inputs: usize) -> PolydatKernel {
let input_names: Vec<String> = (0..n_inputs).map(|i| format!("in{i}")).collect();
let mut asm = PolydatAssembler::new(input_names.clone());
let refs: Vec<WireRef> = input_names.iter().map(WireRef::input).collect();
asm.add_node("sum", Box::new(Sum::new(n_inputs)), refs);
asm.add_output("out", WireRef::node("sum"));
asm.compile().unwrap()
}
fn warm_up(kernel: &mut PolydatKernel) {
black_box(kernel.pull("out")); black_box(kernel.pull("out")); }
fn bench_cell_publish_uncontended(c: &mut Criterion) {
let cell = standalone_cell(Value::U64(0));
c.bench_function("cell/publish_uncontended_u64", |b| {
let mut v = 0u64;
b.iter(|| {
cell.publish(Value::U64(v));
v = v.wrapping_add(1);
});
});
}
fn bench_cell_snapshot_uncontended(c: &mut Criterion) {
let cell = standalone_cell(Value::U64(42));
c.bench_function("cell/snapshot_uncontended_u64", |b| {
b.iter(|| {
black_box(cell.snapshot());
});
});
}
fn bench_cell_publish_contended(c: &mut Criterion) {
let mut group = c.benchmark_group("cell/publish_contended");
for &n_writers in &[1usize, 2, 4, 8] {
group.throughput(Throughput::Elements(n_writers as u64));
group.bench_with_input(
BenchmarkId::from_parameter(n_writers),
&n_writers,
|b, &n_writers| {
b.iter_custom(|iters| {
let per_writer = (iters / n_writers as u64).max(1);
let cell = standalone_cell(Value::U64(0));
let start = Arc::new(std::sync::Barrier::new(n_writers + 1));
let finish = Arc::new(std::sync::Barrier::new(n_writers + 1));
let handles: Vec<_> = (0..n_writers)
.map(|_| {
let cell = cell.clone();
let start = start.clone();
let finish = finish.clone();
thread::spawn(move || {
start.wait();
for i in 0..per_writer {
cell.publish(Value::U64(i));
}
finish.wait();
})
})
.collect();
start.wait();
let t0 = Instant::now();
finish.wait();
let dur = t0.elapsed();
for h in handles {
h.join().unwrap();
}
let observed = cell.revision.load(Ordering::Acquire);
let expected = per_writer * n_writers as u64;
assert_eq!(
observed, expected,
"atomic write rate test: lost increments \
(n_writers={n_writers}, per_writer={per_writer}, \
revision={observed}, expected={expected})"
);
dur
});
},
);
}
group.finish();
}
fn bench_pull_clean_one_scope(c: &mut Criterion) {
let mut group = c.benchmark_group("cell/pull_clean_one_scope");
for &n_cells in &[1usize, 8, 32, 64] {
group.throughput(Throughput::Elements(1));
let mut kernel = build_n_input_kernel(n_cells);
let intent = Arc::new(AtomicU64::new(0));
for i in 0..n_cells {
let cell = cell_in_scope(intent.clone(), i as u8, Value::U64(i as u64));
kernel.state().attach_shared_cell(i, cell);
}
warm_up(&mut kernel);
group.bench_with_input(
BenchmarkId::from_parameter(n_cells),
&n_cells,
|b, _| {
b.iter(|| {
black_box(kernel.pull("out"));
});
},
);
}
group.finish();
}
fn bench_pull_dirty_one_cell(c: &mut Criterion) {
let mut group = c.benchmark_group("cell/pull_dirty_one_cell");
for &n_cells in &[1usize, 8, 32, 64] {
group.throughput(Throughput::Elements(1));
let mut kernel = build_n_input_kernel(n_cells);
let intent = Arc::new(AtomicU64::new(0));
let mut cells: Vec<SharedCell> = Vec::with_capacity(n_cells);
for i in 0..n_cells {
let cell = cell_in_scope(intent.clone(), i as u8, Value::U64(i as u64));
kernel.state().attach_shared_cell(i, cell.clone());
cells.push(cell);
}
warm_up(&mut kernel);
let mut v = 0u64;
group.bench_with_input(
BenchmarkId::from_parameter(n_cells),
&n_cells,
|b, _| {
b.iter(|| {
cells[0].publish(Value::U64(v));
v = v.wrapping_add(1);
black_box(kernel.pull("out"));
});
},
);
}
group.finish();
}
fn bench_pull_clean_multi_scope(c: &mut Criterion) {
let mut group = c.benchmark_group("cell/pull_clean_multi_scope");
for &n_scopes in &[1usize, 2, 4, 8, 16] {
group.throughput(Throughput::Elements(1));
let mut kernel = build_n_input_kernel(n_scopes);
for i in 0..n_scopes {
let intent = Arc::new(AtomicU64::new(0));
let cell = cell_in_scope(intent, 0, Value::U64(i as u64));
kernel.state().attach_shared_cell(i, cell);
}
warm_up(&mut kernel);
group.bench_with_input(
BenchmarkId::from_parameter(n_scopes),
&n_scopes,
|b, _| {
b.iter(|| {
black_box(kernel.pull("out"));
});
},
);
}
group.finish();
}
fn bench_pull_clean_wide_spill(c: &mut Criterion) {
let mut group = c.benchmark_group("cell/pull_clean_wide_spill");
for &n_cells in &[64usize, 128, 256] {
group.throughput(Throughput::Elements(1));
let mut kernel = build_n_input_kernel(n_cells);
let n_words = n_cells.div_ceil(64);
let words: Vec<Arc<AtomicU64>> = (0..n_words)
.map(|_| Arc::new(AtomicU64::new(0)))
.collect();
for i in 0..n_cells {
let word_idx = i / 64;
let bit = (i % 64) as u8;
let cell = cell_in_scope(words[word_idx].clone(), bit, Value::U64(i as u64));
kernel.state().attach_shared_cell(i, cell);
}
warm_up(&mut kernel);
group.bench_with_input(
BenchmarkId::from_parameter(n_cells),
&n_cells,
|b, _| {
b.iter(|| {
black_box(kernel.pull("out"));
});
},
);
}
group.finish();
}
criterion_group!(
benches,
bench_cell_publish_uncontended,
bench_cell_snapshot_uncontended,
bench_cell_publish_contended,
bench_pull_clean_one_scope,
bench_pull_dirty_one_cell,
bench_pull_clean_multi_scope,
bench_pull_clean_wide_spill,
);
criterion_main!(benches);