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.
|
//! Pareto dominance enum and pairwise dominance comparison.
|
||||||
|
|
||||||
use crate::core::evaluation::Evaluation;
|
use crate::core::evaluation::Evaluation;
|
||||||
use crate::core::objective::ObjectiveSpace;
|
use crate::core::objective::{Direction, ObjectiveSpace};
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -62,15 +62,27 @@ pub fn pareto_compare(a: &Evaluation, b: &Evaluation, objectives: &ObjectiveSpac
|
|||||||
(true, true) => {}
|
(true, true) => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
let am = objectives.as_minimization(&a.objectives);
|
// Compare in minimization orientation *without* materializing the two
|
||||||
let bm = objectives.as_minimization(&b.objectives);
|
// 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 a_better_anywhere = false;
|
||||||
let mut b_better_anywhere = false;
|
let mut b_better_anywhere = false;
|
||||||
for (av, bv) in am.iter().zip(bm.iter()) {
|
for ((obj, &av), &bv) in objectives
|
||||||
if av < bv {
|
.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;
|
a_better_anywhere = true;
|
||||||
} else if av > bv {
|
} else if b_better {
|
||||||
b_better_anywhere = true;
|
b_better_anywhere = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user