use core::time::Duration;
use alloc::vec::Vec;
use crate::tune::TuneInputs;
pub struct Bounds<B: TimeBound> {
pub bounds: Vec<B>,
pub launch_overhead: Duration,
}
#[diagnostic::on_unimplemented(
message = "`{Self}` is not a valid bounds generator",
label = "invalid bounds generator"
)]
pub trait BoundsGenerator<K, I: TuneInputs, B: TimeBound>: Send + Sync + 'static {
fn generate<'a>(&self, key: &K, inputs: &I::At<'a>) -> Bounds<B>;
}
impl<K, Func, A, B: TimeBound> BoundsGenerator<K, A, B> for Func
where
A: Clone + Send + Sync + 'static,
K: 'static,
Func: Send + Sync + 'static + Fn(&K, &A) -> Bounds<B>,
{
#[inline]
fn generate<'a>(&self, key: &K, inputs: &<A as TuneInputs>::At<'a>) -> Bounds<B> {
(self)(key, inputs)
}
}
pub trait TimeBound {
fn time_limit(&self) -> Option<Duration>;
}
pub struct AutotuneBound {
pub throughput: f64,
pub threshold: f32,
pub ops_count: usize,
}
impl TimeBound for AutotuneBound {
fn time_limit(&self) -> Option<Duration> {
if self.throughput.is_normal() && self.threshold.is_normal() {
Some(Duration::from_secs_f64(
(self.ops_count as f64 / self.throughput) / self.threshold as f64,
))
} else {
None
}
}
}
impl<B: TimeBound> TimeBound for Vec<B> {
fn time_limit(&self) -> Option<Duration> {
self.iter().filter_map(|b| b.time_limit()).max()
}
}
impl<B: TimeBound> TimeBound for Bounds<B> {
fn time_limit(&self) -> Option<Duration> {
self.bounds
.time_limit()
.map(|limit| limit + self.launch_overhead)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
fn bound(ops_count: usize, throughput: f64, threshold: f32) -> AutotuneBound {
AutotuneBound {
throughput,
threshold,
ops_count,
}
}
#[test]
fn time_limit_is_ops_over_throughput_scaled_by_threshold() {
let limit = bound(8, 4.0, 0.5).time_limit();
assert_eq!(limit, Some(Duration::from_secs(4)));
}
#[test]
fn time_limit_is_none_when_inputs_are_not_normal() {
assert_eq!(bound(8, 0.0, 0.5).time_limit(), None);
assert_eq!(bound(8, f64::NAN, 0.5).time_limit(), None);
assert_eq!(bound(8, f64::INFINITY, 0.5).time_limit(), None);
assert_eq!(bound(8, 4.0, 0.0).time_limit(), None);
}
#[test]
fn vec_time_limit_takes_the_roofline_max_not_min() {
let compute = bound(8, 4.0, 1.0); let memory = bound(8, 8.0, 1.0); let limit = vec![compute, memory].time_limit();
assert_eq!(limit, Some(Duration::from_secs(2)));
}
#[test]
fn vec_time_limit_skips_non_normal_bounds_and_is_none_when_empty() {
let limit = vec![bound(8, 0.0, 1.0), bound(8, 4.0, 1.0)].time_limit();
assert_eq!(limit, Some(Duration::from_secs(2)));
assert_eq!(Vec::<AutotuneBound>::new().time_limit(), None);
}
#[test]
fn bounds_time_limit_adds_launch_overhead() {
let bounds = Bounds {
bounds: vec![bound(8, 4.0, 1.0)], launch_overhead: Duration::from_millis(500),
};
assert_eq!(bounds.time_limit(), Some(Duration::from_millis(2500)));
}
#[test]
fn bounds_time_limit_is_none_without_usable_bounds() {
let bounds = Bounds::<AutotuneBound> {
bounds: vec![],
launch_overhead: Duration::from_millis(500),
};
assert_eq!(bounds.time_limit(), None);
}
}