cubecl_common/profile.rs
1use alloc::boxed::Box;
2use core::fmt::Display;
3
4pub use cubecl_environment::time::{Duration, Instant};
5
6use cubecl_environment::future::DynFut;
7
8/// How a benchmark's execution times are measured.
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
11pub enum TimingMethod {
12 /// Time measurements come from full timing of execution + sync
13 /// calls.
14 System,
15 /// Time measurements come from hardware reported timestamps
16 /// coming from a sync call.
17 Device,
18}
19
20impl Display for TimingMethod {
21 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
22 match self {
23 TimingMethod::System => f.write_str("system"),
24 TimingMethod::Device => f.write_str("device"),
25 }
26 }
27}
28
29/// Start and end point for a profile. Can be turned into a duration.
30#[derive(Debug)]
31pub struct ProfileTicks {
32 start: Instant,
33 end: Instant,
34}
35
36impl ProfileTicks {
37 /// Create a new `ProfileTicks` from a start and end time.
38 pub fn from_start_end(start: Instant, end: Instant) -> Self {
39 Self { start, end }
40 }
41
42 /// Get the duration contained in this `ProfileTicks`.
43 pub fn duration(&self) -> Duration {
44 self.end.duration_since(self.start)
45 }
46
47 /// Get the duration since the epoch start of this `ProfileTicks`.
48 pub fn start_duration_since(&self, epoch: Instant) -> Duration {
49 self.start.duration_since(epoch)
50 }
51
52 /// Get the duration since the epoch end of this `ProfileTicks`.
53 pub fn end_duration_since(&self, epoch: Instant) -> Duration {
54 self.end.duration_since(epoch)
55 }
56}
57
58/// Result from profiling between two measurements. This can either be a duration or a future that resolves to a duration.
59///
60/// The future resolves to [`None`] when the window turned out to carry no
61/// measurement, which a backend can only discover once the device has answered.
62/// That absence is not a zero: zero is the fastest duration there is, so a
63/// caller comparing candidates would let an unmeasured one win every comparison
64/// it enters.
65pub struct ProfileDuration {
66 // The future to read profiling data. For System profiling,
67 // this should be entirely synchronous.
68 future: DynFut<Option<ProfileTicks>>,
69 method: TimingMethod,
70}
71
72impl ProfileDuration {
73 /// The method used to measure the execution time.
74 pub fn timing_method(&self) -> TimingMethod {
75 self.method
76 }
77
78 /// Create a new `ProfileDuration` from a future that resolves to a duration.
79 pub fn new(future: DynFut<Option<ProfileTicks>>, method: TimingMethod) -> ProfileDuration {
80 Self { future, method }
81 }
82
83 /// Create a new `ProfileDuration` straight from a duration.
84 pub fn new_system_time(start: Instant, end: Instant) -> Self {
85 Self::new(
86 Box::pin(async move { Some(ProfileTicks::from_start_end(start, end)) }),
87 TimingMethod::System,
88 )
89 }
90
91 /// Create a new `ProfileDuration` from a future that resolves to a duration.
92 pub fn new_device_time(
93 future: impl Future<Output = ProfileTicks> + Send + 'static,
94 ) -> ProfileDuration {
95 Self::new(
96 Box::pin(async move { Some(future.await) }),
97 TimingMethod::Device,
98 )
99 }
100
101 /// Create a new `ProfileDuration` from a future that may resolve to no
102 /// measurement at all, for a backend that only learns the window carried
103 /// none once the device has answered.
104 pub fn new_device_time_maybe(
105 future: impl Future<Output = Option<ProfileTicks>> + Send + 'static,
106 ) -> ProfileDuration {
107 Self::new(Box::pin(future), TimingMethod::Device)
108 }
109
110 /// Retrieve the future that resolves the profile.
111 pub fn into_future(self) -> DynFut<Option<ProfileTicks>> {
112 self.future
113 }
114
115 /// Resolve the actual duration of the profile, possibly by waiting for the future to complete.
116 ///
117 /// [`None`] when the window carried no measurement. See the type's docs for
118 /// why that is not reported as a zero duration.
119 pub async fn resolve(self) -> Option<ProfileTicks> {
120 self.future.await
121 }
122}