ruda-runtime 0.1.31

Ruda portable host runtime.
use core::time::Duration;

use alloc::vec::Vec;

/// A resource's roofline record: how much of it a run must move, against the
/// peak rate that resource can sustain.
///
/// Neutral by design: nothing here says whether `amount` counts bytes or
/// operations, or where `peak_per_s` came from, measured or modeled. That is
/// what lets [`AutotuneBound`](crate::runtime::tune::AutotuneBound) compose this with
/// a threshold instead of reimplementing the arithmetic.
#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(serde::Serialize, serde::Deserialize)]
pub struct ResourceBound {
    /// How much of the resource the run must move.
    pub amount: usize,
    /// The peak rate this resource can sustain, in the same unit as `amount`
    /// per second.
    pub peak_per_s: f64,
}

impl ResourceBound {
    /// Time `amount` would take running at `peak_per_s`, with no allowance
    /// for anything else the kernel does concurrently.
    ///
    /// `None` for a `peak_per_s` that is zero, negative, `NaN`, or infinite:
    /// none of those describe a real ceiling to divide by.
    pub fn time_at_peak(&self) -> Option<Duration> {
        if self.peak_per_s > 0.0 && self.peak_per_s.is_normal() {
            Some(Duration::from_secs_f64(
                self.amount as f64 / self.peak_per_s,
            ))
        } else {
            None
        }
    }
}

/// The roofline convention this module uses throughout: a run cannot finish
/// faster than its slowest resource allows even under perfect overlap
/// between them, so that resource is the one binding the achievable
/// duration, whatever the others manage. [`binding_achieved`] applies the
/// same reduction to a run's actually measured rates.
///
/// The resource requiring the most time even at its own peak: the entry with
/// the largest [`ResourceBound::time_at_peak`].
///
/// `None` if every entry's `time_at_peak` is `None`, or `bounds` is empty.
pub fn binding_resource(bounds: &[ResourceBound]) -> Option<&ResourceBound> {
    bounds
        .iter()
        .filter(|bound| bound.time_at_peak().is_some())
        .max_by_key(|bound| bound.time_at_peak())
}

/// One resource's achieved rate during a measured run, against its modeled
/// peak.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct AchievedThroughput {
    /// Achieved rate: `amount / duration`.
    pub achieved_per_s: f64,
    /// `achieved_per_s / peak_per_s`, as a fraction. Not clamped to `[0, 1]`:
    /// a run beating the modeled peak is a finding about the model, not an
    /// error to hide.
    pub fraction_of_peak: f64,
}

/// Scores an actual run against a set of [`ResourceBound`]s, one resource at
/// a time.
///
/// `duration` is the run's actual, measured duration, shared by every bound:
/// they all describe the same execution, so the same wall time buys each
/// resource a different achieved rate against its own peak. Results come
/// back in `bounds` order. A zero duration yields `NaN` achieved rates rather
/// than dividing by zero.
pub fn score_resources(duration: Duration, bounds: &[ResourceBound]) -> Vec<AchievedThroughput> {
    bounds
        .iter()
        .map(|bound| {
            let achieved_per_s = if duration.is_zero() {
                f64::NAN
            } else {
                bound.amount as f64 / duration.as_secs_f64()
            };

            AchievedThroughput {
                achieved_per_s,
                fraction_of_peak: achieved_per_s / bound.peak_per_s,
            }
        })
        .collect()
}

/// The resource that actually governed the run's duration: [`binding_resource`]'s
/// reduction applied to achieved scores instead of raw bounds. Every entry
/// here shares one `duration`, so ranking by `fraction_of_peak` gives the
/// same order ranking the underlying bounds by `time_at_peak` would.
///
/// `NaN` entries (a zero duration, or a zero, negative, or non-finite peak)
/// cannot be compared and are skipped; `None` if every entry is such, or
/// `scores` is empty.
pub fn binding_achieved(scores: &[AchievedThroughput]) -> Option<&AchievedThroughput> {
    scores
        .iter()
        .filter(|score| score.fraction_of_peak.is_finite())
        .max_by(|a, b| a.fraction_of_peak.total_cmp(&b.fraction_of_peak))
}