response_time_analysis/wcet/
curve.rs

1use std::cell::RefCell;
2use std::collections::VecDeque;
3use std::iter::FromIterator;
4use std::rc::Rc;
5
6use super::JobCostModel;
7use crate::time::Service;
8
9/// The WCET curve model of a task's cumulative execution-time
10/// demand. Similar to arrival curves, the cost of `n` consecutive
11/// jobs is specified by a vector of cumulative WCET for 1,2,3,...
12/// jobs and extrapolated from there.
13#[derive(Clone, Debug)]
14pub struct Curve {
15    wcet_of_n_jobs: Vec<Service>,
16}
17
18impl Curve {
19    /// Create a new WCET curve model based on the given finite
20    /// prefix of cumulative demand.
21    ///
22    /// The reference input `wcet_of_n_jobs` is assumed to start at
23    /// one job, i.e., the first element of the vector (at offset
24    /// zero) holds the maximum cost of any one job, the second
25    /// element of the vector (at offset one) holds the cumulative
26    /// cost of any two consecutive jobs, the third element of the
27    /// vector (at offset two) holds the cumulative cost of any three
28    /// consecutive jobs, etc.
29    ///
30    /// **Note**: the constructor currently doesn't check whether the
31    /// input is well-formed, so garbage in => garbage out...
32    pub fn new(wcet_of_n_jobs: Vec<Service>) -> Self {
33        Curve { wcet_of_n_jobs }
34    }
35}
36
37impl JobCostModel for Curve {
38    fn cost_of_jobs(&self, n: usize) -> Service {
39        if !self.wcet_of_n_jobs.is_empty() && n > 0 {
40            // resolve large 'n' by super-additivity of cost function
41            let x = n / self.wcet_of_n_jobs.len();
42            let y = n % self.wcet_of_n_jobs.len();
43            let prefix = if x > 0 {
44                self.wcet_of_n_jobs[self.wcet_of_n_jobs.len() - 1] * x as u64
45            } else {
46                Service::none()
47            };
48            let suffix = if y > 0 {
49                // -1 to account for zero-based indexing: offset 0 holds cost of 1 job
50                self.wcet_of_n_jobs[y - 1]
51            } else {
52                Service::none()
53            };
54            prefix + suffix
55        } else {
56            Service::none()
57        }
58    }
59
60    fn job_cost_iter<'a>(&'a self) -> Box<dyn Iterator<Item = Service> + 'a> {
61        Box::new((1..).map(move |n| self.cost_of_jobs(n) - self.cost_of_jobs(n - 1)))
62    }
63
64    fn least_wcet(&self, n: usize) -> Service {
65        if n > 0 {
66            let mut least = self.wcet_of_n_jobs[0];
67            for i in 1..self.wcet_of_n_jobs.len().min(n) {
68                least = least.min(self.wcet_of_n_jobs[i] - self.wcet_of_n_jobs[i - 1])
69            }
70            least
71        } else {
72            Service::none()
73        }
74    }
75}
76
77impl Curve {
78    pub fn from_trace(job_costs: impl Iterator<Item = Service>, max_n: usize) -> Curve {
79        let mut cost_of = Vec::with_capacity(max_n);
80        let mut window: VecDeque<Service> = VecDeque::with_capacity(max_n + 1);
81
82        // consider all observed costs in the trace
83        for c in job_costs {
84            // add job cost to sliding window
85            window.push_back(c);
86            // trim sliding window if necessary
87            if window.len() > max_n {
88                window.pop_front();
89            }
90
91            // look at all job costs in the sliding window and keep track of total cost
92            let mut total_cost = Service::none();
93            for (i, k) in window.iter().enumerate() {
94                total_cost += *k;
95                if cost_of.len() <= i {
96                    // we have not yet seen (i + 1) costs in a row -> first sample
97                    cost_of.push(total_cost)
98                } else {
99                    // update total cost of (i+1) jobs
100                    cost_of[i] = cost_of[i].max(total_cost)
101                }
102            }
103        }
104
105        Curve {
106            wcet_of_n_jobs: cost_of,
107        }
108    }
109
110    fn extrapolate_next(&self) -> Service {
111        let n = self.wcet_of_n_jobs.len();
112        assert!(n >= 2);
113        // Upper-bound cost of n jobs as the sum of the bounds on the costs of
114        // n-k jobs and k jobs. Since we don't store n=0, this is offset by one.
115        (0..=(n / 2))
116            .map(|k| self.wcet_of_n_jobs[k] + self.wcet_of_n_jobs[n - k - 1])
117            .min()
118            .unwrap()
119    }
120
121    pub fn extrapolate(&mut self, n: usize) {
122        // We need at least three samples to extrapolate, so let's do nothing if we have fewer.
123        if self.wcet_of_n_jobs.len() >= 3 {
124            while self.wcet_of_n_jobs.len() < n - 1 {
125                self.wcet_of_n_jobs.push(self.extrapolate_next())
126            }
127        }
128    }
129}
130
131impl FromIterator<Service> for Curve {
132    fn from_iter<I: IntoIterator<Item = Service>>(iter: I) -> Curve {
133        let mut wcets: Vec<Service> = iter.into_iter().collect();
134        // ensure the cost function is monotonic
135        for i in 1..wcets.len() {
136            wcets[i] = wcets[i].max(wcets[i - 1]);
137        }
138        Curve::new(wcets)
139    }
140}
141
142/// A variant of [Curve] that uses interior mutability to cache extrapolations.
143/// Functionality equivalent to [Curve], but faster on average if
144/// extrapolation occurs often.
145#[derive(Clone, Debug)]
146pub struct ExtrapolatingCurve {
147    prefix: Rc<RefCell<Curve>>,
148}
149
150impl ExtrapolatingCurve {
151    pub fn new(costfn: Curve) -> Self {
152        ExtrapolatingCurve {
153            prefix: Rc::new(RefCell::new(costfn)),
154        }
155    }
156}
157
158impl JobCostModel for ExtrapolatingCurve {
159    fn cost_of_jobs(&self, n: usize) -> Service {
160        let mut costfn = self.prefix.borrow_mut();
161        costfn.extrapolate(n + 1);
162        costfn.cost_of_jobs(n)
163    }
164
165    fn job_cost_iter<'a>(&'a self) -> Box<dyn Iterator<Item = Service> + 'a> {
166        Box::new((1..).map(move |n| self.cost_of_jobs(n) - self.cost_of_jobs(n - 1)))
167    }
168
169    fn least_wcet(&self, n: usize) -> Service {
170        // Don't need to extrapolate for this; the least delta is
171        // fully determined by the initial prefix.
172        self.prefix.borrow().least_wcet(n)
173    }
174}