From 4aab5c7029e6747bdcb7bcd65af09ebe9cfd45f6 Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Thu, 14 May 2026 06:36:47 -0600 Subject: [PATCH] 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> 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) --- src/pareto/sort.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/pareto/sort.rs b/src/pareto/sort.rs index a3dd576..d14c248 100644 --- a/src/pareto/sort.rs +++ b/src/pareto/sort.rs @@ -51,11 +51,14 @@ pub fn non_dominated_sort( .iter() .map(|c| c.evaluation.constraint_violation) .collect(); - let oriented: Vec> = population - .iter() - .map(|c| objectives.as_minimization(&c.evaluation.objectives)) - .collect(); let m = objectives.len(); + // Flat `n * m` buffer rather than `Vec>`: 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 = Vec::with_capacity(n * m); + for c in population { + oriented.extend_from_slice(&objectives.as_minimization(&c.evaluation.objectives)); + } let mut dominates: Vec> = vec![Vec::new(); n]; let mut dominated_by_count: Vec = vec![0; n]; @@ -69,7 +72,7 @@ pub fn non_dominated_sort( for i in 0..n { let ai_feasible = feasible[i]; let ai_violation = violation[i]; - let ai = &oriented[i]; + let ai = &oriented[i * m..i * m + m]; for j in (i + 1)..n { let bi_feasible = feasible[j]; let bi_violation = violation[j]; @@ -89,7 +92,7 @@ pub fn non_dominated_sort( } } (true, true) => { - let bj = &oriented[j]; + let bj = &oriented[j * m..j * m + m]; let mut a_better_anywhere = false; let mut b_better_anywhere = false; for k in 0..m {