perf(pareto): flatten non_dominated_sort objective buffer (1.29M -> 1.10M instr)

The O(n^2) pair loop reads oriented[j] for every j; with Vec<Vec<f64>>
that chased a separate heap allocation per individual. A flat n*m buffer
keeps those reads contiguous and sequential in j.

non_dominated_sort_2d n=200: 1_288_072 -> 1_096_738 (-15%, 1.17x); n=50
-19%. nsga2 one-generation -5.7%. Combined with the earlier antisymmetry
fix, n=200 is down 55% from the original 2.46M. Pure data-layout change --
output bit-identical, all 606 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-14 06:38:03 -06:00
co-authored by Claude Opus 4.7
parent 37821bdd3d
commit 4aab5c7029
+9 -6
View File
@@ -51,11 +51,14 @@ pub fn non_dominated_sort<D>(
.iter() .iter()
.map(|c| c.evaluation.constraint_violation) .map(|c| c.evaluation.constraint_violation)
.collect(); .collect();
let oriented: Vec<Vec<f64>> = population
.iter()
.map(|c| objectives.as_minimization(&c.evaluation.objectives))
.collect();
let m = objectives.len(); let m = objectives.len();
// Flat `n * m` buffer rather than `Vec<Vec<f64>>`: the O(n²) pair loop
// reads `oriented[j]` for every `j`, and a contiguous layout keeps those
// reads sequential instead of chasing one heap allocation per individual.
let mut oriented: Vec<f64> = Vec::with_capacity(n * m);
for c in population {
oriented.extend_from_slice(&objectives.as_minimization(&c.evaluation.objectives));
}
let mut dominates: Vec<Vec<usize>> = vec![Vec::new(); n]; let mut dominates: Vec<Vec<usize>> = vec![Vec::new(); n];
let mut dominated_by_count: Vec<usize> = vec![0; n]; let mut dominated_by_count: Vec<usize> = vec![0; n];
@@ -69,7 +72,7 @@ pub fn non_dominated_sort<D>(
for i in 0..n { for i in 0..n {
let ai_feasible = feasible[i]; let ai_feasible = feasible[i];
let ai_violation = violation[i]; let ai_violation = violation[i];
let ai = &oriented[i]; let ai = &oriented[i * m..i * m + m];
for j in (i + 1)..n { for j in (i + 1)..n {
let bi_feasible = feasible[j]; let bi_feasible = feasible[j];
let bi_violation = violation[j]; let bi_violation = violation[j];
@@ -89,7 +92,7 @@ pub fn non_dominated_sort<D>(
} }
} }
(true, true) => { (true, true) => {
let bj = &oriented[j]; let bj = &oriented[j * m..j * m + m];
let mut a_better_anywhere = false; let mut a_better_anywhere = false;
let mut b_better_anywhere = false; let mut b_better_anywhere = false;
for k in 0..m { for k in 0..m {