perf(non_dominated_sort): cache oriented values + inline pareto_compare
The Deb fast non-dominated sort calls `pareto_compare` twice for every (i, j) pair, and each `pareto_compare` call invokes `ObjectiveSpace::as_minimization` twice — so for an N-point population that's 4·N·(N-1) fresh `Vec<f64>` allocations per sort. At N=100 with thousands of generations across the compare harness, this dominated the per-generation cost of every Pareto-based MOEA. Cache `as_minimization`/feasibility/violation once per individual up front, then inline the dominance test against those cached arrays. The output (per-pair dominance outcome and the per-i `dominates` lists) is bit-identical to `pareto_compare`. gungraun (instructions): - non_dominated_sort_2d n=50: 852 317 → 198 574 (-77 %, 4.3×) - non_dominated_sort_2d n=200: 13 513 271 → 2 601 813 (-81 %, 5.2×) Wall-clock (compare harness, 10-seed mean): - NSGA-II / ZDT1: 268 → 65 ms (4.1×) - NSGA-II / ZDT3: 267 → 65 ms (4.1×) - NSGA-II / DTLZ2: 344 → 106 ms (3.2×) - NSGA-II / Rastrigin: 260 → 71 ms (3.7×) - NSGA-III / DTLZ2: 318 → 122 ms (2.6×) - NSGA-III / DTLZ1: 303 → 122 ms (2.5×) - SMS-EMOA / DTLZ2: 1413 → 1369 ms (small additional win on top of HV) - AGE-MOEA / DTLZ1: 430 → 229 ms (1.9×, on top of the AGE-MOEA caching) - HypE / DTLZ2: 80 → 44 ms (1.8×)
This commit is contained in:
+63
-9
@@ -2,7 +2,6 @@
|
||||
|
||||
use crate::core::candidate::Candidate;
|
||||
use crate::core::objective::ObjectiveSpace;
|
||||
use crate::pareto::dominance::{Dominance, pareto_compare};
|
||||
|
||||
/// Partition the population into Pareto fronts by dominance rank.
|
||||
///
|
||||
@@ -19,24 +18,79 @@ pub fn non_dominated_sort<D>(
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
// Precompute the per-individual feasibility, violation, and
|
||||
// minimization-oriented objective vectors. The naïve formulation
|
||||
// calls `pareto_compare` (and therefore `as_minimization`) twice for
|
||||
// every pair, allocating two fresh Vec<f64>s per call; doing it once
|
||||
// up front cuts that to one allocation per individual.
|
||||
let feasible: Vec<bool> = population
|
||||
.iter()
|
||||
.map(|c| c.evaluation.is_feasible())
|
||||
.collect();
|
||||
let violation: Vec<f64> = population
|
||||
.iter()
|
||||
.map(|c| c.evaluation.constraint_violation)
|
||||
.collect();
|
||||
let oriented: Vec<Vec<f64>> = population
|
||||
.iter()
|
||||
.map(|c| objectives.as_minimization(&c.evaluation.objectives))
|
||||
.collect();
|
||||
let m = objectives.len();
|
||||
|
||||
let mut dominates: Vec<Vec<usize>> = vec![Vec::new(); n];
|
||||
let mut dominated_by_count: Vec<usize> = vec![0; n];
|
||||
let mut fronts: Vec<Vec<usize>> = Vec::new();
|
||||
let mut first_front: Vec<usize> = Vec::new();
|
||||
|
||||
for i in 0..n {
|
||||
let ai_feasible = feasible[i];
|
||||
let ai_violation = violation[i];
|
||||
let ai = &oriented[i];
|
||||
for j in 0..n {
|
||||
if i == j {
|
||||
continue;
|
||||
}
|
||||
match pareto_compare(
|
||||
&population[i].evaluation,
|
||||
&population[j].evaluation,
|
||||
objectives,
|
||||
) {
|
||||
Dominance::Dominates => dominates[i].push(j),
|
||||
Dominance::DominatedBy => dominated_by_count[i] += 1,
|
||||
_ => {}
|
||||
let bi_feasible = feasible[j];
|
||||
let bi_violation = violation[j];
|
||||
// Inline the body of `pareto_compare`. We only care about
|
||||
// `Dominates` vs `DominatedBy`; `Equal` and `NonDominated`
|
||||
// are no-ops here.
|
||||
let dominates_outcome = match (ai_feasible, bi_feasible) {
|
||||
(true, false) => Some(true), // i dominates j
|
||||
(false, true) => Some(false), // i is dominated
|
||||
(false, false) => {
|
||||
if ai_violation < bi_violation {
|
||||
Some(true)
|
||||
} else if ai_violation > bi_violation {
|
||||
Some(false)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
(true, true) => {
|
||||
let bj = &oriented[j];
|
||||
let mut a_better_anywhere = false;
|
||||
let mut b_better_anywhere = false;
|
||||
for k in 0..m {
|
||||
let av = ai[k];
|
||||
let bv = bj[k];
|
||||
if av < bv {
|
||||
a_better_anywhere = true;
|
||||
} else if av > bv {
|
||||
b_better_anywhere = true;
|
||||
}
|
||||
}
|
||||
match (a_better_anywhere, b_better_anywhere) {
|
||||
(true, false) => Some(true),
|
||||
(false, true) => Some(false),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
};
|
||||
match dominates_outcome {
|
||||
Some(true) => dominates[i].push(j),
|
||||
Some(false) => dominated_by_count[i] += 1,
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
if dominated_by_count[i] == 0 {
|
||||
|
||||
Reference in New Issue
Block a user