perf(pareto): skip already-dominated points in pareto_front
`pareto_front` re-scanned every candidate from scratch. Track a `dominated` bitset instead: whenever `i`'s scan finds `i` dominates `j`, mark `j` so the outer loop skips `j` outright when it reaches it. The inner check now reads both directions of `pareto_compare` — the objective scan already computes both flags, so this is ~free. Bit-identical, including under NaN-intransitive dominance: a mark is only ever set from a direct pairwise `pareto_compare` result, never inferred transitively. Also strict-or-neutral on work — the marks can only ever let the outer loop *skip*, never add a scan. Whole-program callgrind Ir for the compare_profile benchmark: 169,440,644,233 -> 165,311,562,939 (-2.44%); `pareto_front` self-Ir 44.1B -> 39.9B.
This commit is contained in:
+26
-9
@@ -55,8 +55,19 @@ pub fn pareto_front<D: Clone>(
|
|||||||
oriented.extend_from_slice(&objectives.as_minimization(&c.evaluation.objectives));
|
oriented.extend_from_slice(&objectives.as_minimization(&c.evaluation.objectives));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `dominated[j]` is set the moment some candidate is found to dominate
|
||||||
|
// `j`. Whenever `i`'s scan finds `i` dominates `j`, mark `j` so the
|
||||||
|
// outer loop can skip `j` entirely when it reaches it. This never does
|
||||||
|
// more work than the plain scan — the marks only ever let us *skip* —
|
||||||
|
// and it stays bit-identical even under NaN-intransitive dominance:
|
||||||
|
// a mark is set only from a direct pairwise `pareto_compare` result,
|
||||||
|
// never inferred transitively.
|
||||||
|
let mut dominated: Vec<bool> = vec![false; n];
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
'outer: for i in 0..n {
|
'outer: for i in 0..n {
|
||||||
|
if dominated[i] {
|
||||||
|
continue 'outer;
|
||||||
|
}
|
||||||
let ai_feasible = feasible[i];
|
let ai_feasible = feasible[i];
|
||||||
let ai_violation = violation[i];
|
let ai_violation = violation[i];
|
||||||
let ai = &oriented[i * m..i * m + m];
|
let ai = &oriented[i * m..i * m + m];
|
||||||
@@ -64,13 +75,13 @@ pub fn pareto_front<D: Clone>(
|
|||||||
if i == j {
|
if i == j {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// `i` is kept only if no `j` dominates it — i.e. no `j` for which
|
// Inline both directions of `pareto_compare`: `j` dominating
|
||||||
// `pareto_compare(a_i, a_j)` would be `DominatedBy`. This inlines
|
// `i` excludes `i`; `i` dominating `j` lets us skip `j`'s own
|
||||||
// exactly that one outcome of `pareto_compare`.
|
// scan later.
|
||||||
let dominated_by_j = match (ai_feasible, feasible[j]) {
|
let (i_dominates_j, j_dominates_i) = match (ai_feasible, feasible[j]) {
|
||||||
(true, false) => false,
|
(true, false) => (true, false),
|
||||||
(false, true) => true,
|
(false, true) => (false, true),
|
||||||
(false, false) => ai_violation > violation[j],
|
(false, false) => (ai_violation < violation[j], ai_violation > violation[j]),
|
||||||
(true, true) => {
|
(true, true) => {
|
||||||
let aj = &oriented[j * m..j * m + m];
|
let aj = &oriented[j * m..j * m + m];
|
||||||
let mut a_better_anywhere = false;
|
let mut a_better_anywhere = false;
|
||||||
@@ -84,12 +95,18 @@ pub fn pareto_front<D: Clone>(
|
|||||||
b_better_anywhere = true;
|
b_better_anywhere = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
b_better_anywhere && !a_better_anywhere
|
(
|
||||||
|
a_better_anywhere && !b_better_anywhere,
|
||||||
|
b_better_anywhere && !a_better_anywhere,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if dominated_by_j {
|
if j_dominates_i {
|
||||||
continue 'outer;
|
continue 'outer;
|
||||||
}
|
}
|
||||||
|
if i_dominates_j {
|
||||||
|
dominated[j] = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
out.push(population[i].clone());
|
out.push(population[i].clone());
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user