perf(age_moea): cache lp_norm + maintain nearest-neighbor incrementally
The splitting-front survival selection in AGE-MOEA recomputed two expensive things per while-iteration: * `lp_norm(translated[i], p)` for every remaining i — even though the value is constant across iterations. * `nearest_neighbor_distance(i, …, &keep, p)` — a fresh full scan over the keep list, even though only one new candidate was added since the last scan. Both are `powf`-heavy in the L_p frame. Compute lp_norm once per candidate at function entry. Maintain a `nearest[]` array seeded from the initial keep set and updated on every pick by a single `min(nearest[i], lp_distance(i, pick, p))` per remaining i. That cuts the score loop from O(R · K · M) to O(R · M) per iteration, with the dominant powf calls in lp_distance counted once per (remaining, pick) pair instead of per (remaining, full-keep). Wall-clock (compare harness, 10-seed mean): - AGE-MOEA / DTLZ1: 2266 → 430 ms on top of v0.3.0 baseline (5.3×) - AGE-MOEA / ZDT3: 935 → 376 ms (2.5×)
This commit is contained in:
@@ -187,17 +187,34 @@ fn environmental_selection<D: Clone>(
|
|||||||
// proximity = ||translated||_p
|
// proximity = ||translated||_p
|
||||||
// diversity = nearest-neighbor distance in the same L_p frame
|
// diversity = nearest-neighbor distance in the same L_p frame
|
||||||
// among already-selected + splitting members.
|
// among already-selected + splitting members.
|
||||||
|
//
|
||||||
|
// Two caches make this much cheaper than the textbook formulation:
|
||||||
|
// * `prox[i]` — `lp_norm(translated[i], p)` is constant across
|
||||||
|
// iterations, so compute it once per splitting-front member.
|
||||||
|
// * `nearest[i]` — the nearest-keep distance only ever decreases
|
||||||
|
// when a new candidate is picked, so we maintain it
|
||||||
|
// incrementally: seed it from `selected`, then on every pick
|
||||||
|
// update each remaining `i`'s nearest by taking
|
||||||
|
// `min(nearest[i], lp_distance(translated[i], translated[pick], p))`.
|
||||||
|
//
|
||||||
|
// That cuts the score loop from O(R · K · M) per iteration (where
|
||||||
|
// R = remaining count, K = current keep count) to O(R · M) per
|
||||||
|
// iteration, with the dominant `powf` calls in lp_distance counted
|
||||||
|
// once per (remaining, pick) pair instead of per (remaining, all-keep).
|
||||||
let mut keep = selected.clone();
|
let mut keep = selected.clone();
|
||||||
let mut remaining: Vec<usize> = splitting.clone();
|
let mut remaining: Vec<usize> = splitting.clone();
|
||||||
|
let prox: Vec<f64> = (0..combined.len())
|
||||||
|
.map(|i| lp_norm(&translated[i], p))
|
||||||
|
.collect();
|
||||||
|
let mut nearest: Vec<f64> = (0..combined.len())
|
||||||
|
.map(|i| nearest_neighbor_distance(i, &translated, &keep, p))
|
||||||
|
.collect();
|
||||||
while keep.len() < n {
|
while keep.len() < n {
|
||||||
// Compute scores for every remaining candidate; pick the one with
|
// Pick the remaining candidate with the largest score.
|
||||||
// the largest combined score.
|
|
||||||
let mut best_idx: Option<usize> = None;
|
let mut best_idx: Option<usize> = None;
|
||||||
let mut best_score = f64::NEG_INFINITY;
|
let mut best_score = f64::NEG_INFINITY;
|
||||||
for &i in &remaining {
|
for &i in &remaining {
|
||||||
let prox = lp_norm(&translated[i], p);
|
let score = nearest[i] / (prox[i].max(1e-12));
|
||||||
let div = nearest_neighbor_distance(i, &translated, &keep, p);
|
|
||||||
let score = div / (prox.max(1e-12));
|
|
||||||
if score > best_score {
|
if score > best_score {
|
||||||
best_score = score;
|
best_score = score;
|
||||||
best_idx = Some(i);
|
best_idx = Some(i);
|
||||||
@@ -208,6 +225,14 @@ fn environmental_selection<D: Clone>(
|
|||||||
Some(pick) => {
|
Some(pick) => {
|
||||||
keep.push(pick);
|
keep.push(pick);
|
||||||
remaining.retain(|&i| i != pick);
|
remaining.retain(|&i| i != pick);
|
||||||
|
// Update each surviving remaining's nearest-keep using
|
||||||
|
// just the distance to the new pick.
|
||||||
|
for &i in &remaining {
|
||||||
|
let d = lp_distance(&translated[i], &translated[pick], p);
|
||||||
|
if d < nearest[i] {
|
||||||
|
nearest[i] = d;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user