Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 2 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,14 @@ derive_builder = "0.20.2"
la-stack = "0.1.3"
tracing = "0.1.44"
rustc-hash = "2.1.1" # Fast non-cryptographic hashing for performance
smallvec = { version = "1.15.1", features = [
"serde",
] } # Stack allocation for small collections
smallvec = { version = "1.15.1", features = [ "serde" ] } # Stack allocation for small collections
num-traits = "0.2.19"
ordered-float = { version = "5.1.0", features = [ "serde" ] }
rand = "0.10.0"
serde = { version = "1.0.228", features = [ "derive" ] }
slotmap = { version = "1.1.1", features = [ "serde" ] }
thiserror = "2.0.18"
uuid = { version = "1.20.0", features = [ "v4", "serde" ] }
uuid = { version = "1.20.0", features = [ "v4", "serde", "fast-rng" ] }

[dev-dependencies]
approx = "0.5.1"
Expand Down
144 changes: 131 additions & 13 deletions benches/ci_performance_suite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@

use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use delaunay::geometry::util::generate_random_points_seeded;
use delaunay::prelude::DelaunayTriangulation;
use delaunay::prelude::{ConstructionOptions, DelaunayTriangulation, RetryPolicy};

Check failure on line 29 in benches/ci_performance_suite.rs

View workflow job for this annotation

GitHub Actions / performance-regression

unresolved imports `delaunay::prelude::ConstructionOptions`, `delaunay::prelude::RetryPolicy`
use delaunay::vertex;
use std::hint::black_box;
use std::num::NonZeroUsize;
use tracing::error;

Check failure on line 33 in benches/ci_performance_suite.rs

View workflow job for this annotation

GitHub Actions / performance-regression

unresolved import `tracing`

/// Common sample sizes used across all CI performance benchmarks
const COUNTS: &[usize] = &[10, 25, 50];
Expand All @@ -40,6 +41,19 @@
.unwrap_or(false)
}

fn bench_seed_search_enabled() -> bool {
std::env::var("DELAUNAY_BENCH_SEED_SEARCH")
.map(|value| value != "0")
.unwrap_or(false)
}

fn bench_seed_search_limit() -> usize {
std::env::var("DELAUNAY_BENCH_SEED_SEARCH_LIMIT")
.ok()
.and_then(|value| value.parse::<usize>().ok())
.unwrap_or(2000)
}

/// Fixed seeds for deterministic triangulation generation across benchmark runs.
/// Using seeded random number generation reduces variance in performance measurements
/// and improves regression detection accuracy in CI environments.
Expand All @@ -49,8 +63,94 @@
macro_rules! benchmark_tds_new_dimension {
($dim:literal, $func_name:ident, $seed:literal) => {
/// Benchmark triangulation creation for D-dimensional triangulations
#[expect(
clippy::too_many_lines,
reason = "Keep benchmark configuration, seed search, and error reporting together"
)]
fn $func_name(c: &mut Criterion) {
let counts = COUNTS;

// Opt-in helper for discovering stable seeds without paying Criterion warmup/
// measurement cost per seed.
//
// NOTE: This helper is intentionally per (dim, count) benchmark case.
// It returns early on the first successful seed (and panics on failure),
// so it is meant to be run with a Criterion filter that selects a single
// case, for example:
//
// cargo bench --bench ci_performance_suite -- 'tds_new_3d/tds_new/50'
//
// Because the base seed is derived from `count`, a seed that works for one
// count may still fail for a different count.
//
// We avoid `std::process::exit` here so that destructors run and Criterion
// can clean up state on both success and failure.
if bench_seed_search_enabled() {
let bounds = (-100.0, 100.0);
let filters: Vec<String> = std::env::args()
.skip(1)
.filter(|arg| !arg.starts_with('-'))
.collect();

for &count in counts {
let bench_id =
format!("tds_new_{}d/tds_new/{}", stringify!($dim), count);

if !filters.is_empty() && !filters.iter().any(|filter| bench_id.contains(filter)) {
continue;
}

let seed = ($seed as u64).wrapping_add(count as u64);
let limit = bench_seed_search_limit();

for offset in 0..limit {
let candidate_seed = seed.wrapping_add(offset as u64);
let points = generate_random_points_seeded::<f64, $dim>(
count,
bounds,
candidate_seed,
)
.expect(concat!(
"generate_random_points_seeded failed for ",
stringify!($dim),
"D"
));
let vertices = points.iter().map(|p| vertex!(*p)).collect::<Vec<_>>();

let options =
ConstructionOptions::default().with_retry_policy(RetryPolicy::Shuffled {
attempts: NonZeroUsize::new(6)
.expect("retry attempts must be non-zero"),
base_seed: Some(candidate_seed),
});

if DelaunayTriangulation::<_, (), (), $dim>::new_with_options(

Check failure on line 127 in benches/ci_performance_suite.rs

View workflow job for this annotation

GitHub Actions / performance-regression

no function or associated item named `new_with_options` found for struct `DelaunayTriangulation<K, U, V, D>` in the current scope

Check failure on line 127 in benches/ci_performance_suite.rs

View workflow job for this annotation

GitHub Actions / performance-regression

no function or associated item named `new_with_options` found for struct `DelaunayTriangulation<K, U, V, D>` in the current scope

Check failure on line 127 in benches/ci_performance_suite.rs

View workflow job for this annotation

GitHub Actions / performance-regression

no function or associated item named `new_with_options` found for struct `DelaunayTriangulation<K, U, V, D>` in the current scope

Check failure on line 127 in benches/ci_performance_suite.rs

View workflow job for this annotation

GitHub Actions / performance-regression

no function or associated item named `new_with_options` found for struct `DelaunayTriangulation<K, U, V, D>` in the current scope
&vertices,
options,
)
.is_ok()
{
println!(
"seed_search_found dim={} count={} seed={}",
$dim, count, candidate_seed
);
return;
}
}

panic!(
"seed_search_failed dim={} count={} start_seed={} limit={}",
$dim,
count,
seed,
limit
);
}

// No filter matched this benchmark function; do nothing.
return;
}

let mut group = c.benchmark_group(concat!("tds_new_", stringify!($dim), "d"));

// Set smaller sample sizes for higher dimensions to keep CI times reasonable
Expand All @@ -66,18 +166,36 @@
group.bench_with_input(BenchmarkId::new("tds_new", count), &count, |b, &count| {
// Reduce variance: pre-generate deterministic inputs outside the measured loop,
// then benchmark only triangulation construction.
let points =
generate_random_points_seeded::<f64, $dim>(count, (-100.0, 100.0), $seed)
.expect(concat!(
"generate_random_points_seeded failed for ",
stringify!($dim),
"D"
));
//
// Note: Use per-count seeds so that each benchmark case has its own deterministic
// point set. This avoids a single pathological input (e.g. 3D/50) aborting the
// entire suite.
let bounds = (-100.0, 100.0);
let seed = ($seed as u64).wrapping_add(count as u64);

let points = generate_random_points_seeded::<f64, $dim>(count, bounds, seed)
.expect(concat!(
"generate_random_points_seeded failed for ",
stringify!($dim),
"D"
));
let vertices = points.iter().map(|p| vertex!(*p)).collect::<Vec<_>>();
let sample_points = points.iter().take(5).collect::<Vec<_>>();

// In benchmarks we compile in release mode, where the default retry policy is
// disabled. For deterministic CI benchmarks we opt into a small number of
// shuffled retries to avoid aborting the suite on rare non-convergent repair
// cases.
let options = ConstructionOptions::default().with_retry_policy(RetryPolicy::Shuffled {
attempts: NonZeroUsize::new(6).expect("retry attempts must be non-zero"),
base_seed: Some(seed),
});

b.iter(|| {
match DelaunayTriangulation::<_, (), (), $dim>::new(&vertices) {
match DelaunayTriangulation::<_, (), (), $dim>::new_with_options(

Check failure on line 195 in benches/ci_performance_suite.rs

View workflow job for this annotation

GitHub Actions / performance-regression

no function or associated item named `new_with_options` found for struct `DelaunayTriangulation<K, U, V, D>` in the current scope

Check failure on line 195 in benches/ci_performance_suite.rs

View workflow job for this annotation

GitHub Actions / performance-regression

no function or associated item named `new_with_options` found for struct `DelaunayTriangulation<K, U, V, D>` in the current scope

Check failure on line 195 in benches/ci_performance_suite.rs

View workflow job for this annotation

GitHub Actions / performance-regression

no function or associated item named `new_with_options` found for struct `DelaunayTriangulation<K, U, V, D>` in the current scope

Check failure on line 195 in benches/ci_performance_suite.rs

View workflow job for this annotation

GitHub Actions / performance-regression

no function or associated item named `new_with_options` found for struct `DelaunayTriangulation<K, U, V, D>` in the current scope
&vertices,
options,
) {
Ok(dt) => {
black_box(dt);
}
Expand All @@ -87,8 +205,8 @@
error!(
dim = $dim,
count,
seed = $seed,
bounds = ?(-100.0, 100.0),
seed,
bounds = ?bounds,
sample_points = ?sample_points,
error = %error,
"DelaunayTriangulation::new failed"
Expand All @@ -99,8 +217,8 @@
$dim,
$dim,
count,
$seed,
(-100.0, 100.0)
seed,
bounds
);
}
}
Expand Down
Loading
Loading