From 2bd8f8fc11a5c267a588a8cf12a9a7c54fc9c665 Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Thu, 14 May 2026 12:02:15 -0600 Subject: [PATCH] perf(pareto): precompute oriented buffers in pareto_front MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `pareto_front` was still the naive O(n²) formulation: a raw double loop calling `pareto_compare` for every ordered pair, re-deriving feasibility and the minimization-oriented objective values on every comparison. Apply the same precompute pattern `non_dominated_sort` already uses: hoist per-individual `feasible` / `violation` / `oriented` (a flat n*m buffer) out of the loop, then run a branchless inlined dominance check over the contiguous buffer. The kept set and its order are unchanged. Whole-program callgrind Ir for the `compare_profile` benchmark: 221,836,742,708 -> 173,803,642,945 (-21.65%); `pareto_front` self-Ir 92.1B -> 44.1B. --- src/pareto/front.rs | 63 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/src/pareto/front.rs b/src/pareto/front.rs index 1148076..716ced5 100644 --- a/src/pareto/front.rs +++ b/src/pareto/front.rs @@ -2,7 +2,6 @@ use crate::core::candidate::Candidate; use crate::core::objective::ObjectiveSpace; -use crate::pareto::dominance::{Dominance, pareto_compare}; /// Return all candidates that are not dominated by any other candidate. /// @@ -31,20 +30,68 @@ pub fn pareto_front( population: &[Candidate], objectives: &ObjectiveSpace, ) -> Vec> { + let n = population.len(); + if n == 0 { + return Vec::new(); + } + + // Precompute per-individual feasibility, violation, and the + // minimization-oriented objective vectors once, mirroring + // `non_dominated_sort`. The naïve formulation called `pareto_compare` + // (and therefore `as_minimization`) for every ordered pair, re-deriving + // all of this on every comparison; precomputing turns the O(n²) inner + // loop into a branchless scan over a contiguous buffer. + let feasible: Vec = population + .iter() + .map(|c| c.evaluation.is_feasible()) + .collect(); + let violation: Vec = population + .iter() + .map(|c| c.evaluation.constraint_violation) + .collect(); + let m = objectives.len(); + let mut oriented: Vec = Vec::with_capacity(n * m); + for c in population { + oriented.extend_from_slice(&objectives.as_minimization(&c.evaluation.objectives)); + } + let mut out = Vec::new(); - 'outer: for (i, a) in population.iter().enumerate() { - for (j, b) in population.iter().enumerate() { + 'outer: for i in 0..n { + let ai_feasible = feasible[i]; + let ai_violation = violation[i]; + let ai = &oriented[i * m..i * m + m]; + for j in 0..n { if i == j { continue; } - if matches!( - pareto_compare(&a.evaluation, &b.evaluation, objectives), - Dominance::DominatedBy - ) { + // `i` is kept only if no `j` dominates it — i.e. no `j` for which + // `pareto_compare(a_i, a_j)` would be `DominatedBy`. This inlines + // exactly that one outcome of `pareto_compare`. + let dominated_by_j = match (ai_feasible, feasible[j]) { + (true, false) => false, + (false, true) => true, + (false, false) => ai_violation > violation[j], + (true, true) => { + let aj = &oriented[j * m..j * m + m]; + let mut a_better_anywhere = false; + let mut b_better_anywhere = false; + for k in 0..m { + let av = ai[k]; + let bv = aj[k]; + if av < bv { + a_better_anywhere = true; + } else if av > bv { + b_better_anywhere = true; + } + } + b_better_anywhere && !a_better_anywhere + } + }; + if dominated_by_j { continue 'outer; } } - out.push(a.clone()); + out.push(population[i].clone()); } out }