perf(pareto): make pareto_compare allocation-free
The objective-comparison branch of `pareto_compare` materialized two `Vec<f64>`s per call via `ObjectiveSpace::as_minimization`. Because `pareto_compare` runs O(n²) times across the multi-objective algorithms, that per-call allocation pair dominated the whole `compare` workload. Replace it with an allocation-free per-objective scan that branches on `Objective::direction` directly: for a Maximize axis "a beats b" is just `av > bv`, bit-identical to `-av < -bv` after orientation. The result is unchanged for every input. Whole-program callgrind Ir for the `compare_profile` benchmark: 357,060,633,544 -> 221,836,742,708 (-37.87%).
This commit is contained in:
+19
-7
@@ -1,7 +1,7 @@
|
||||
//! Pareto dominance enum and pairwise dominance comparison.
|
||||
|
||||
use crate::core::evaluation::Evaluation;
|
||||
use crate::core::objective::ObjectiveSpace;
|
||||
use crate::core::objective::{Direction, ObjectiveSpace};
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -62,15 +62,27 @@ pub fn pareto_compare(a: &Evaluation, b: &Evaluation, objectives: &ObjectiveSpac
|
||||
(true, true) => {}
|
||||
}
|
||||
|
||||
let am = objectives.as_minimization(&a.objectives);
|
||||
let bm = objectives.as_minimization(&b.objectives);
|
||||
|
||||
// Compare in minimization orientation *without* materializing the two
|
||||
// oriented `Vec<f64>`s that `as_minimization` would allocate.
|
||||
// `pareto_compare` is called O(n²) times across the multi-objective
|
||||
// algorithms, so a per-call heap-allocation pair dominates the whole
|
||||
// program. For a Maximize objective, "a beats b" is just `av > bv` —
|
||||
// bit-identical to `-av < -bv` after orientation.
|
||||
let mut a_better_anywhere = false;
|
||||
let mut b_better_anywhere = false;
|
||||
for (av, bv) in am.iter().zip(bm.iter()) {
|
||||
if av < bv {
|
||||
for ((obj, &av), &bv) in objectives
|
||||
.objectives
|
||||
.iter()
|
||||
.zip(a.objectives.iter())
|
||||
.zip(b.objectives.iter())
|
||||
{
|
||||
let (a_better, b_better) = match obj.direction {
|
||||
Direction::Minimize => (av < bv, av > bv),
|
||||
Direction::Maximize => (av > bv, av < bv),
|
||||
};
|
||||
if a_better {
|
||||
a_better_anywhere = true;
|
||||
} else if av > bv {
|
||||
} else if b_better {
|
||||
b_better_anywhere = true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user