From 740965958fd3f69f4d53d4bc9745ba7b6713bc3a Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Thu, 14 May 2026 11:27:12 -0600 Subject: [PATCH] perf(pareto): make pareto_compare allocation-free MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The objective-comparison branch of `pareto_compare` materialized two `Vec`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%). --- src/pareto/dominance.rs | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/pareto/dominance.rs b/src/pareto/dominance.rs index ba8aa6a..ff2b980 100644 --- a/src/pareto/dominance.rs +++ b/src/pareto/dominance.rs @@ -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`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; } }