Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
271 changes: 164 additions & 107 deletions benches/ci_performance_suite.rs

Large diffs are not rendered by default.

100 changes: 78 additions & 22 deletions benches/circumsphere_containment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,23 @@ use delaunay::prelude::generators::generate_random_points_seeded;
use delaunay::prelude::query::*;
use std::hint::black_box;

fn bench_result<T, E: std::fmt::Display>(result: Result<T, E>, context: &str) -> T {
match result {
Ok(value) => value,
Err(error) => {
eprintln!("{context}: {error}");
std::process::exit(1);
}
}
}

fn bench_option<T>(option: Option<T>, context: &str) -> T {
option.unwrap_or_else(|| {
eprintln!("{context}");
std::process::exit(1);
})
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

/// Generate a standard D-dimensional simplex (D+1 vertices)
///
/// Creates a simplex with vertices at:
Expand All @@ -41,17 +58,19 @@ fn standard_simplex<const D: usize>() -> Vec<Point<f64, D>> {

/// Generate a random 3D simplex (tetrahedron) for benchmarking using seeded generation
fn generate_random_simplex_3d(seed: u64) -> Vec<Point<f64, 3>> {
generate_random_points_seeded(4, (-10.0, 10.0), seed)
.expect("Failed to generate random simplex points")
bench_result(
generate_random_points_seeded(4, (-10.0, 10.0), seed),
"failed to generate random simplex points",
)
}

/// Generate a random 3D test point using seeded generation
fn generate_random_test_point_3d(seed: u64) -> Point<f64, 3> {
generate_random_points_seeded(1, (-5.0, 5.0), seed)
.expect("Failed to generate random test point")
.into_iter()
.next()
.expect("Expected exactly one test point")
let points = bench_result(
generate_random_points_seeded(1, (-5.0, 5.0), seed),
"failed to generate random test point",
);
bench_option(points.into_iter().next(), "expected exactly one test point")
}

/// Benchmark with many random queries
Expand All @@ -60,33 +79,40 @@ fn benchmark_random_queries(c: &mut Criterion) {
let simplex_points = generate_random_simplex_3d(42);

// Generate many test points using seeded generation for reproducible results
let test_points = generate_random_points_seeded(1000, (-5.0, 5.0), 123)
.expect("Failed to generate random test points");
let test_points = bench_result(
generate_random_points_seeded(1000, (-5.0, 5.0), 123),
"failed to generate random test points",
);

c.bench_function("random/insphere_1000_queries", |b| {
b.iter(|| {
for test_point in &test_points {
black_box(insphere(black_box(&simplex_points), black_box(*test_point)).unwrap());
black_box(bench_result(
insphere(black_box(&simplex_points), black_box(*test_point)),
"insphere query failed",
));
}
});
});

c.bench_function("random/insphere_distance_1000_queries", |b| {
b.iter(|| {
for test_point in &test_points {
black_box(
insphere_distance(black_box(&simplex_points), black_box(*test_point)).unwrap(),
);
black_box(bench_result(
insphere_distance(black_box(&simplex_points), black_box(*test_point)),
"insphere_distance query failed",
));
}
});
});

c.bench_function("random/insphere_lifted_1000_queries", |b| {
b.iter(|| {
for test_point in &test_points {
black_box(
insphere_lifted(black_box(&simplex_points), black_box(*test_point)).unwrap(),
);
black_box(bench_result(
insphere_lifted(black_box(&simplex_points), black_box(*test_point)),
"insphere_lifted query failed",
));
}
});
});
Expand All @@ -96,13 +122,28 @@ fn benchmark_random_queries(c: &mut Criterion) {
macro_rules! bench_simplex {
($c:ident, $dim:literal, $simplex:expr, $pt:expr) => {{
$c.bench_function(concat!($dim, "d/insphere"), |b| {
b.iter(|| black_box(insphere(black_box(&$simplex), black_box($pt)).unwrap()))
b.iter(|| {
black_box(bench_result(
insphere(black_box(&$simplex), black_box($pt)),
"insphere benchmark query failed",
))
})
});
$c.bench_function(concat!($dim, "d/insphere_distance"), |b| {
b.iter(|| black_box(insphere_distance(black_box(&$simplex), black_box($pt)).unwrap()))
b.iter(|| {
black_box(bench_result(
insphere_distance(black_box(&$simplex), black_box($pt)),
"insphere_distance benchmark query failed",
))
})
});
$c.bench_function(concat!($dim, "d/insphere_lifted"), |b| {
b.iter(|| black_box(insphere_lifted(black_box(&$simplex), black_box($pt)).unwrap()))
b.iter(|| {
black_box(bench_result(
insphere_lifted(black_box(&$simplex), black_box($pt)),
"insphere_lifted benchmark query failed",
))
})
});
}};
}
Expand All @@ -112,18 +153,33 @@ macro_rules! bench_edge_case {
($c:ident, $dim:literal, $case:literal, $simplex:expr, $pt:expr) => {{
$c.bench_function(
concat!("edge_cases_", $dim, "d/", $case, "_insphere"),
|b| b.iter(|| black_box(insphere(black_box(&$simplex), black_box($pt)).unwrap())),
|b| {
b.iter(|| {
black_box(bench_result(
insphere(black_box(&$simplex), black_box($pt)),
"edge-case insphere benchmark query failed",
))
})
},
);
$c.bench_function(
concat!("edge_cases_", $dim, "d/", $case, "_distance"),
|b| {
b.iter(|| {
black_box(insphere_distance(black_box(&$simplex), black_box($pt)).unwrap())
black_box(bench_result(
insphere_distance(black_box(&$simplex), black_box($pt)),
"edge-case insphere_distance benchmark query failed",
))
})
},
);
$c.bench_function(concat!("edge_cases_", $dim, "d/", $case, "_lifted"), |b| {
b.iter(|| black_box(insphere_lifted(black_box(&$simplex), black_box($pt)).unwrap()))
b.iter(|| {
black_box(bench_result(
insphere_lifted(black_box(&$simplex), black_box($pt)),
"edge-case insphere_lifted benchmark query failed",
))
})
});
}};
}
Expand Down
32 changes: 26 additions & 6 deletions benches/cold_path_predicates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@ use delaunay::prelude::generators::generate_random_points_seeded;
use delaunay::prelude::query::*;
use std::hint::black_box;

fn bench_result<T, E: std::fmt::Display>(result: Result<T, E>, context: &str) -> T {
match result {
Ok(value) => value,
Err(error) => {
eprintln!("{context}: {error}");
std::process::exit(1);
}
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

Comment thread
coderabbitai[bot] marked this conversation as resolved.
/// Deterministic seed for query-point generation in the hot path.
const HOT_SEED: u64 = 0xC01D_BEEF_0000_CAFE_u64;
/// Deterministic seed for query-point generation in the near-boundary group.
Expand Down Expand Up @@ -65,8 +75,10 @@ fn standard_simplex<const D: usize>() -> Vec<Point<f64, D>> {
/// Uses the range `[-10, 10]` against a unit simplex so that the Shewchuk
/// errbound comfortably resolves the sign in Stage 1.
fn hot_queries<const D: usize>() -> Vec<Point<f64, D>> {
generate_random_points_seeded(HOT_QUERIES, (-10.0, 10.0), HOT_SEED)
.expect("failed to generate hot-path query points")
bench_result(
generate_random_points_seeded(HOT_QUERIES, (-10.0, 10.0), HOT_SEED),
"failed to generate hot-path query points",
)
}

/// Generate near-boundary query points for dimension `D`.
Expand All @@ -77,21 +89,29 @@ fn near_boundary_queries<const D: usize>() -> Vec<Point<f64, D>> {
// Centered near the circumsphere radius of the standard simplex (~0.5 for
// the D = 3 unit case); the exact value is unimportant — we just want a
// high rate of errbound-ambiguous inputs.
generate_random_points_seeded(NEAR_BOUNDARY_QUERIES, (0.40, 0.60), NEAR_BOUNDARY_SEED)
.expect("failed to generate near-boundary query points")
bench_result(
generate_random_points_seeded(NEAR_BOUNDARY_QUERIES, (0.40, 0.60), NEAR_BOUNDARY_SEED),
"failed to generate near-boundary query points",
)
}

/// Run `insphere` across `queries` against `simplex`, black-boxing each result.
fn run_insphere<const D: usize>(simplex: &[Point<f64, D>], queries: &[Point<f64, D>]) {
for q in queries {
black_box(insphere(black_box(simplex), black_box(*q)).unwrap());
black_box(bench_result(
insphere(black_box(simplex), black_box(*q)),
"insphere query failed",
));
}
}

/// Run `insphere_lifted` across `queries` against `simplex`, black-boxing each result.
fn run_insphere_lifted<const D: usize>(simplex: &[Point<f64, D>], queries: &[Point<f64, D>]) {
for q in queries {
black_box(insphere_lifted(black_box(simplex), black_box(*q)).unwrap());
black_box(bench_result(
insphere_lifted(black_box(simplex), black_box(*q)),
"insphere_lifted query failed",
));
}
}

Expand Down
81 changes: 56 additions & 25 deletions benches/large_scale_performance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,16 @@ macro_rules! bench_info {
}};
}

fn bench_result<T, E: std::fmt::Display>(result: Result<T, E>, context: &str) -> T {
match result {
Ok(value) => value,
Err(error) => {
eprintln!("{context}: {error}");
std::process::exit(1);
}
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

/// Memory usage information for benchmarking (in KiB)
#[cfg_attr(
not(feature = "bench-logging"),
Expand All @@ -138,13 +148,19 @@ fn get_memory_usage() -> u64 {
bench_info!("Memory measurements in KiB (sysinfo::Process::memory() / 1024)");
});

let pid = sysinfo::get_current_pid().expect("Failed to get current PID");
let pid = bench_result(sysinfo::get_current_pid(), "failed to get current PID");
let sys = SYS.get_or_init(|| {
Mutex::new(System::new_with_specifics(
RefreshKind::nothing().with_processes(ProcessRefreshKind::nothing().with_memory()),
))
});
let mut system = sys.lock().expect("lock System");
let mut system = match sys.lock() {
Ok(system) => system,
Err(error) => {
eprintln!("failed to lock System: {error}");
std::process::exit(1);
}
};
system.refresh_processes_specifics(
ProcessesToUpdate::Some(&[pid]),
true,
Expand Down Expand Up @@ -189,7 +205,10 @@ fn benchmark_retry_attempts() -> NonZeroUsize {
.unwrap_or(6)
.max(1);

NonZeroUsize::new(attempts).expect("attempts clamped to >= 1")
let Some(attempts) = NonZeroUsize::new(attempts) else {
unreachable!("attempts clamped to >= 1");
};
attempts
})
}

Expand All @@ -213,13 +232,12 @@ fn construct_triangulation<const D: usize>(
vertices: &[Vertex<f64, (), D>],
seed: u64,
) -> DelaunayTriangulation<AdaptiveKernel<f64>, (), (), D> {
DelaunayTriangulation::new_with_options(vertices, construction_options(seed)).unwrap_or_else(
|err| {
panic!(
"Failed to create triangulation (dim={D}, n_vertices={}, seed={seed}): {err:?}",
vertices.len()
)
},
bench_result(
DelaunayTriangulation::new_with_options(vertices, construction_options(seed)),
&format!(
"failed to create triangulation (dim={D}, n_vertices={}, seed={seed})",
vertices.len()
),
)
}

Expand All @@ -228,8 +246,10 @@ fn measure_construction_with_memory<const D: usize>(n_points: usize, seed: u64)
let mem_before = get_memory_usage();

// Generate points and vertices (setup overhead)
let points = generate_random_points_seeded::<f64, D>(n_points, (-100.0, 100.0), seed)
.expect("Failed to generate points");
let points = bench_result(
generate_random_points_seeded::<f64, D>(n_points, (-100.0, 100.0), seed),
"failed to generate points",
);
let vertices: Vec<_> = points.into_iter().map(|p| vertex!(p)).collect();

// Measure memory before triangulation construction to isolate allocation
Expand Down Expand Up @@ -288,9 +308,10 @@ fn bench_construction<const D: usize>(c: &mut Criterion, dimension_name: &str, n
b.iter_batched(
|| {
// Setup: Generate points (not measured)
let points =
generate_random_points_seeded::<f64, D>(n_points, (-100.0, 100.0), seed)
.expect("Failed to generate points");
let points = bench_result(
generate_random_points_seeded::<f64, D>(n_points, (-100.0, 100.0), seed),
"failed to generate points",
);
points.into_iter().map(|p| vertex!(p)).collect::<Vec<_>>()
},
|vertices| {
Expand Down Expand Up @@ -362,8 +383,10 @@ fn bench_validation<const D: usize>(c: &mut Criterion, dimension_name: &str, n_p
}

// Pre-generate triangulation for validation benchmarks
let points = generate_random_points_seeded::<f64, D>(n_points, (-100.0, 100.0), seed)
.expect("Failed to generate points");
let points = bench_result(
generate_random_points_seeded::<f64, D>(n_points, (-100.0, 100.0), seed),
"failed to generate points",
);
let vertices: Vec<_> = points.into_iter().map(|p| vertex!(p)).collect();
let dt = construct_triangulation::<D>(&vertices, seed);
let tri = dt.as_triangulation();
Expand All @@ -374,8 +397,10 @@ fn bench_validation<const D: usize>(c: &mut Criterion, dimension_name: &str, n_p
group.bench_function("validate_topology", |b| {
b.iter(|| {
// Level 3 topology check (manifold-with-boundary + Euler characteristic)
tri.is_valid()
.expect("triangulation should be structurally valid during validation benchmark");
bench_result(
tri.is_valid(),
"triangulation should be structurally valid during validation benchmark",
);
});
});

Expand Down Expand Up @@ -404,8 +429,10 @@ fn bench_neighbor_queries<const D: usize>(
let seed = seed_for_case::<D>(n_points);

// Pre-generate triangulation
let points = generate_random_points_seeded::<f64, D>(n_points, (-100.0, 100.0), seed)
.expect("Failed to generate points");
let points = bench_result(
generate_random_points_seeded::<f64, D>(n_points, (-100.0, 100.0), seed),
"failed to generate points",
);
let vertices: Vec<_> = points.into_iter().map(|p| vertex!(p)).collect();
let dt = construct_triangulation::<D>(&vertices, seed);
let tds = dt.tds();
Expand Down Expand Up @@ -448,8 +475,10 @@ fn bench_vertex_iteration<const D: usize>(
let seed = seed_for_case::<D>(n_points);

// Pre-generate triangulation
let points = generate_random_points_seeded::<f64, D>(n_points, (-100.0, 100.0), seed)
.expect("Failed to generate points");
let points = bench_result(
generate_random_points_seeded::<f64, D>(n_points, (-100.0, 100.0), seed),
"failed to generate points",
);
let vertices: Vec<_> = points.into_iter().map(|p| vertex!(p)).collect();
let dt = construct_triangulation::<D>(&vertices, seed);
let tds = dt.tds();
Expand Down Expand Up @@ -483,8 +512,10 @@ fn bench_cell_iteration<const D: usize>(c: &mut Criterion, dimension_name: &str,
let seed = seed_for_case::<D>(n_points);

// Pre-generate triangulation
let points = generate_random_points_seeded::<f64, D>(n_points, (-100.0, 100.0), seed)
.expect("Failed to generate points");
let points = bench_result(
generate_random_points_seeded::<f64, D>(n_points, (-100.0, 100.0), seed),
"failed to generate points",
);
let vertices: Vec<_> = points.into_iter().map(|p| vertex!(p)).collect();
let dt = construct_triangulation::<D>(&vertices, seed);
let tds = dt.tds();
Expand Down
Loading
Loading