cpu_timer 0.1.1

Precise CPU timers with backup to std::time, for in-library timing
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
//a Imports
use crate::{BaseTimer, TArch, TDesc, TraceCount, TraceValue};

//a AccArray
//tp AccArray
/// An [AccArray] can be used to accumulate the times taken to execute
/// different branches of code, from a common start point. Each branch
/// is allocated a different index into the AccArray. It can also count
/// the entries.
///
/// The AccArray is generic on whether to use the CPU-specific
/// architcture timer implementation, the value to accumulate times in
/// (e.g. u64), the value to use to count occurrences (e.g. u32), and
/// the number of trace points in the vec
///
/// The 'start' method is called first; when a branch completed it
/// invokes the 'acc_n' method with its index, and the delta time since
/// the start is added to that start's accumulator.
///
/// Invoking the 'acc_n' method does not update the 'start' time, and it
/// is quite sensible to issue multiple 'acc' invocations (with
/// different index values) for a given 'start' invocation.
///
/// The 'acc_n_restart' method, though, performs the same accumulation
/// and it *does* update the 'start' time; this can be used to
/// accumulate elapsed time between stages.
///
/// An AccArray can be generated for any N, for an accumulator value
/// of (), u8, u16, u32, u64, u128 and usize, and for a counter value
/// of (), u8, u16, u32, u64, usize. If a value of () is used then the
/// count or delta accumulator are effectively always 0.
#[derive(Debug, Clone, Copy)]
pub struct AccArray<const S: bool, T: TraceValue, C: TraceCount, const N: usize>
where
    TDesc<S>: TArch,
{
    base: BaseTimer<S>,
    accs: [T; N],
    cnts: [C; N],
}

//ip Default for AccArray
impl<const S: bool, T, C, const N: usize> std::default::Default for AccArray<S, T, C, N>
where
    TDesc<S>: TArch,
    T: TraceValue,
    C: TraceCount,
    [T; N]: Default,
    [C; N]: Default,
{
    fn default() -> Self {
        let base = BaseTimer::default();
        let accs = <[T; N]>::default();
        let cnts = <[C; N]>::default();
        Self { base, accs, cnts }
    }
}

//ip Display for AccArray
impl<const S: bool, T, C, const N: usize> std::fmt::Display for AccArray<S, T, C, N>
where
    TDesc<S>: TArch,
    T: TraceValue + std::fmt::Display + std::ops::Div<C>,
    <T as std::ops::Div<C>>::Output: std::fmt::Display,
    C: TraceCount + std::fmt::Display + PartialEq<C>,
    [T; N]: Default,
    [C; N]: Default,
{
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        let def_c: C = C::default();
        write! {fmt, "["}?;
        for i in 0..N {
            if i != 0 {
                write! {fmt, ", "}?;
            }
            if self.cnts[i] == def_c {
                write!(fmt, "({}, {}, -)", self.accs[i], self.cnts[i])?;
            } else {
                write!(
                    fmt,
                    "({}, {}, {})",
                    self.accs[i],
                    self.cnts[i],
                    self.accs[i] / self.cnts[i]
                )?;
            }
        }
        write! {fmt, "]"}
    }
}

//ip AccArray
impl<const S: bool, T, C, const N: usize> AccArray<S, T, C, N>
where
    TDesc<S>: TArch,
    T: TraceValue,
    C: TraceCount,
{
    //mp clear
    /// Clear the timer and accumulated values
    pub fn clear(&mut self) {
        unsafe { *self = std::mem::zeroed() };
    }

    //mp start
    /// Start the underlying timer
    #[inline(always)]
    pub fn start(&mut self) {
        self.base.start();
    }

    //mp acc_n
    /// Add the ticks on exit to a specific region
    #[inline(always)]
    pub fn acc_n(&mut self, index: usize) {
        if index < N {
            let delta: u64 = self.base.elapsed();
            self.accs[index] = self.accs[index].sat_add(delta);
            self.cnts[index].sat_inc();
        }
    }

    //mp acc_n_restart
    /// Add the ticks on exit to a specific region
    #[inline(always)]
    pub fn acc_n_restart(&mut self, index: usize) {
        if index < N {
            let delta = self.base.elapsed_and_update();
            self.accs[index] = self.accs[index].sat_add(delta);
            self.cnts[index].sat_inc();
        }
    }

    //mp accs
    /// Return the accumulated values
    pub fn accs(&self) -> &[T; N] {
        &self.accs
    }

    //mp cnts
    /// Return the accumulated counts
    pub fn cnts(&self) -> &[C; N] {
        &self.cnts
    }
}

//a AccVec
//tp AccVec
/// An [AccVec] can be used to count and accumulate the times taken to
/// execute different branches of code, from a common start point. It
/// uses a `Vec` internally, and can be dynamically sized; it is
/// otherwise very similar in operation to an [AccArray].
///
/// The AccVec is generic on whether to use the CPU-specific
/// architcture timer implementation, the value to accumulate times in
/// (e.g. u64), the value to use to count occurrences (e.g. u32).
///
/// An AccVec can be created with a specific capacity - and memory is
/// allocated at this time for that capacity; when it is cleared, the
/// AccVec is emptied, but the contents are not freed.
///
/// In addition to the `acc_n` and `acc_n_restart` methods of the
/// AccArray, the AccVec adds `acc_push` and `acc_push_restart`, which
/// append to the current vector (and return the index used).
///
/// # Example accumulated times
///
/// An AccVec can be used to simply generate an arbitrarily long trace
/// of accumulated elapsed times (using `acc_push`):
///
/// ```
/// # use cpu_timer::AccVec;
/// let mut t = AccVec::<true, u32,()>::default();
/// t.start();
/// // do something!
/// t.acc_push();
/// // do something more!
/// t.acc_push();
/// // do something even more!
/// t.acc_push();
///
/// println!("The first step took {} ticks", t.acc_cnts()[0].0);
/// println!("The first and second steps took {} ticks", t.acc_cnts()[1].0);
/// println!("The first thru third steps took {} ticks", t.acc_cnts()[2].0);
///
/// ```
///
/// # Example individual times
///
/// An AccVec can be used to simply generate an arbitrarily long trace
/// of elapsed times (using `acc_push_restart`):
///
/// ```
/// # use cpu_timer::AccVec;
/// let mut t = AccVec::<true, u32, u8>::default();
/// t.start();
/// // do something!
/// t.acc_push_restart();
/// // do something more!
/// t.acc_push_restart();
/// // do something even more!
/// t.acc_push_restart();
///
/// println!("The first step took {} ticks", t.acc_cnts()[0].0);
/// println!("The second step took {} ticks", t.acc_cnts()[1].0);
/// println!("The third step took {} ticks", t.acc_cnts()[2].0);
///
/// for ac in t.acc_cnts() {
///     assert_eq!(ac.1, 1, "Each step occurred precisely once");
/// }
/// ```
///
/// # Example accumulate times over many executions
///
/// An AccVec can be used to accumulate an arbitrarily long trace
/// of elapsed times (using `acc_push_restart`):
///
/// ```
/// # use cpu_timer::AccVec;
/// let mut t = AccVec::<true, u32, u32>::default();
///
/// for i in 0..1000 {
///   t.start();
///   if i%3 == 0 {
///      // do something!
///   }
///   t.acc_push_restart();
///   if i%5 == 0 {
///      // do something!
///   }
///   t.acc_push_restart();
///   if i%2 == 0 {
///      // do something!
///      t.acc_push_restart();
///   }
/// }
///
/// let ac = t.all_acc_cnts();
/// assert_eq!(ac.len(), 3, "There are three accumulated values");
/// println!("The first step took an average of {} ticks", ac[0].0 / ac[0].1);
/// println!("The second step took an average of {} ticks", ac[1].0 / ac[1].1);
/// println!("The third step took an average of {} ticks", ac[2].0 / ac[2].1);
///
/// assert_eq!(ac[0].1, 1000, "First step occurred 1000 times");
/// assert_eq!(ac[1].1, 1000, "Second step occurred 1000 times");
/// assert_eq!(ac[2].1, 500, "*Last* step occurred 500 times");
///
/// assert_eq!(t.acc_cnts().len(), 2, "The last iteration did not do the `i%2` push!");
/// ```
/// # Example accumulate times for different operations over many executions
///
/// ```
/// # use cpu_timer::AccVec;
/// let mut t = AccVec::<true, u32, u32>::with_capacity(6);
///
/// for i in 0..1000 {
///   t.start();
///   for j in &["a", "", "bb", "ccc", "dddd", "eeeee", "bb", "ccc"] {
///      let k = j.chars().count();
///      t.acc_n_restart(k);
///   }
/// }
///
/// let ac = t.all_acc_cnts();
/// assert_eq!(ac.len(), 6, "There are five accumulated values, the given capacity");
/// for i in 0..6 {
///     let avg = ac[i].0 / ac[i].1;
///     println!("Counting {i} characters took an average of {} ticks", avg);
/// }
/// ```
#[derive(Debug, Clone)]
pub struct AccVec<const S: bool, T: TraceValue, C: TraceCount>
where
    TDesc<S>: TArch,
{
    base: BaseTimer<S>,
    index: usize,
    acc_cnts: Vec<(T, C)>,
}

//ip Default for AccVec
impl<const S: bool, T, C> std::default::Default for AccVec<S, T, C>
where
    TDesc<S>: TArch,
    T: TraceValue,
    C: TraceCount,
{
    fn default() -> Self {
        let base = BaseTimer::default();
        let acc_cnts = vec![];
        let index = 0;
        Self {
            base,
            index,
            acc_cnts,
        }
    }
}

//ip Display for AccVec
impl<const S: bool, T, C> std::fmt::Display for AccVec<S, T, C>
where
    TDesc<S>: TArch,
    T: TraceValue + std::fmt::Display + std::ops::Div<C>,
    <T as std::ops::Div<C>>::Output: std::fmt::Display,
    C: TraceCount + std::fmt::Display + PartialEq<C>,
{
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        let def_c: C = C::default();
        write! {fmt, "["}?;
        for (i, ac) in self.acc_cnts.iter().enumerate() {
            if i != 0 {
                write! {fmt, ", "}?;
            }
            if ac.1 == def_c {
                write!(fmt, "({}, {}, -)", ac.0, ac.1)?;
            } else {
                write!(fmt, "({}, {}, {})", ac.0, ac.1, ac.0 / ac.1)?;
            }
        }
        write! {fmt, "]"}
    }
}

//ip AccVec
impl<const S: bool, T, C> AccVec<S, T, C>
where
    TDesc<S>: TArch,
    T: TraceValue,
    C: TraceCount,
{
    //mp with_capacity
    /// Create a new AccVec of a certain size
    pub fn with_capacity(n: usize) -> Self {
        let mut s = Self::default();
        s.acc_cnts = vec![(T::default(), C::default()); n];
        s
    }

    //mp clear
    /// Clear the timer and accumulated values
    pub fn clear(&mut self) {
        self.index = 0;
        self.acc_cnts.clear();
    }

    //mp start
    /// Start the underlying timer
    #[inline(always)]
    pub fn start(&mut self) {
        self.base.start();
        self.index = 0;
    }

    //mp acc_n
    /// Calculate the ticks elapsed, and accumulate that in the specified
    /// entry in the store
    ///
    /// If the entry is beyond the capacity of the store, then this
    /// does nothing
    #[inline(always)]
    pub fn acc_n(&mut self, index: usize) {
        if let Some(ac) = self.acc_cnts.get_mut(index) {
            let delta: u64 = self.base.elapsed();
            ac.0 = ac.0.sat_add(delta);
            ac.1.sat_inc();
        }
    }

    //mp acc_n_restart
    /// Calculate the ticks elapsed and restart the timer, and
    /// accumulate that in the specified entry in the store
    ///
    /// If the entry is beyond the capacity of the store, then this
    /// just restarts the timer
    #[inline(always)]
    pub fn acc_n_restart(&mut self, index: usize) {
        if let Some(ac) = self.acc_cnts.get_mut(index) {
            let delta = self.base.elapsed_and_update();
            ac.0 = ac.0.sat_add(delta);
            ac.1.sat_inc();
        } else {
            self.base.start();
        }
    }

    //mp acc_push
    /// Calculate the ticks elapsed, and accumulate that in the next
    /// entry in the store
    ///
    /// This will extend the underlying store if required
    #[inline(always)]
    pub fn acc_push(&mut self) -> usize {
        let n = self.acc_cnts.len();
        if n > self.index {
            self.acc_n(self.index);
            let n = self.index;
            self.index += 1;
            n
        } else {
            let delta: u64 = self.base.elapsed();
            let delta = T::default().sat_add(delta);
            let mut cnt = C::default();
            cnt.sat_inc();
            self.acc_cnts.push((delta, cnt));
            self.index = n + 1;
            n
        }
    }

    //mp acc_push_restart
    /// Calculate the ticks elapsed and restart the timer, and
    /// accumulate that in the next entry in the store
    ///
    /// This will extend the underlying store if required
    #[inline(always)]
    pub fn acc_push_restart(&mut self) -> usize {
        let n = self.acc_cnts.len();
        if n > self.index {
            self.acc_n_restart(self.index);
            let n = self.index;
            self.index += 1;
            n
        } else {
            let delta = self.base.elapsed_and_update();
            let delta = T::default().sat_add(delta);
            let mut cnt = C::default();
            cnt.sat_inc();
            self.acc_cnts.push((delta, cnt));
            self.index = n + 1;
            n
        }
    }

    //mp all_acc_cnts
    /// Return *all* the accumulated values and counts
    ///
    /// This should be used if the `acc_n` methods are used, but not
    /// `acc_push`
    pub fn all_acc_cnts(&self) -> &[(T, C)] {
        &self.acc_cnts
    }

    //mp acc_cnts
    /// Return the accumulated values and counts, up to the last
    /// pushed
    ///
    /// The underlying store may have *more* values, perhaps from
    /// previous executions. This only returns up to the last value
    /// `pushed` since the last start.
    pub fn acc_cnts(&self) -> &[(T, C)] {
        &self.acc_cnts[0..self.index]
    }
}