From 532d4a54dbb63f64cfed031952e461ec77e6db3b Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Thu, 14 May 2026 06:31:45 -0600 Subject: [PATCH] perf(pareto): sort crowding-distance keys without Vec> indirection (-4.5%) crowding_distance sorted bare front indices with a comparator that chased two Vec> indirections per comparison. Extracting (objective value, front position) tuples into a buffer reused across objectives keeps the hot comparator a single f64 compare. crowding_distance_2d n=200: 181_493 -> 173_286 (-4.5%); n=50 -4.5%. Stable sort over the (value, index) pairs preserves the original tie-order, so the output is bit-identical -- all 606 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/pareto/crowding.rs | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/pareto/crowding.rs b/src/pareto/crowding.rs index aee2e07..581e190 100644 --- a/src/pareto/crowding.rs +++ b/src/pareto/crowding.rs @@ -55,33 +55,35 @@ pub fn crowding_distance( .map(|&idx| objectives.as_minimization(&population[idx].evaluation.objectives)) .collect(); + // Reused across objectives: (objective-k value, front position). Sorting + // these tuples directly keeps the hot comparator a single `f64` compare + // instead of chasing two `Vec>` indirections per comparison. + let mut keyed: Vec<(f64, usize)> = Vec::with_capacity(n); + #[allow(clippy::needless_range_loop)] // `k` indexes into nested vectors below. for k in 0..m { - // Sort indices into `front` by objective k. - let mut order: Vec = (0..n).collect(); - order.sort_by(|&a, &b| { - oriented[a][k] - .partial_cmp(&oriented[b][k]) - .unwrap_or(std::cmp::Ordering::Equal) - }); + keyed.clear(); + keyed.extend((0..n).map(|i| (oriented[i][k], i))); + keyed.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal)); - distance[order[0]] = f64::INFINITY; - distance[order[n - 1]] = f64::INFINITY; + let first = keyed[0].1; + let last = keyed[n - 1].1; + distance[first] = f64::INFINITY; + distance[last] = f64::INFINITY; - let f_min = oriented[order[0]][k]; - let f_max = oriented[order[n - 1]][k]; - let span = f_max - f_min; + let span = keyed[n - 1].0 - keyed[0].0; if span == 0.0 { continue; } for i in 1..n - 1 { - if distance[order[i]] == f64::INFINITY { + let idx = keyed[i].1; + if distance[idx] == f64::INFINITY { continue; } - let prev = oriented[order[i - 1]][k]; - let next = oriented[order[i + 1]][k]; - distance[order[i]] += (next - prev) / span; + let prev = keyed[i - 1].0; + let next = keyed[i + 1].0; + distance[idx] += (next - prev) / span; } }