Skip to main content

burn_backend/backend/
profile.rs

1pub use burn_std::profile::{ProfileDuration, ProfileTicks, TimingMethod};
2use burn_std::{ExecutionError, backtrace::BackTrace, profile::Instant};
3
4/// How a [profiled window](crate::Backend::profile) treats the work a backend
5/// still holds in a queue when the window closes.
6#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
7pub struct ProfileOptions {
8    flush: bool,
9}
10
11impl ProfileOptions {
12    /// Execute everything queued before closing the window, so the
13    /// measurement holds all the work the closure registered.
14    ///
15    /// A backend that batches operations (fusion) otherwise leaves the
16    /// closure's last operations queued past the window, and cuts nothing
17    /// short — its batching is what production runs. Flushing forces the batch
18    /// out at the window's end, which measures the closure's whole work at the
19    /// cost of running it as production would not.
20    pub fn flush(mut self) -> Self {
21        self.flush = true;
22        self
23    }
24
25    /// Whether the window flushes queued work before closing.
26    pub fn flushes(&self) -> bool {
27        self.flush
28    }
29}
30
31/// An open profiling window on a backend, handed out by
32/// [`profile_start`](crate::Backend::profile_start) and passed back to
33/// [`profile_end`](crate::Backend::profile_end) to close it.
34///
35/// Windows nest and overlap: a backend keys each on its token.
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
37pub struct ProfileToken {
38    /// The backend's own identifier for the window.
39    pub id: u64,
40    /// The backend's own identifier for **where** the window was opened — a
41    /// stream, a queue — carried so it can be closed there.
42    ///
43    /// Opaque to this crate, and `0` for a backend that has only one such
44    /// place. A window belongs to where it was opened, not to whoever closes
45    /// it: the split pair exists for work launched from somewhere else, so
46    /// the two calls can land on different threads, and a close that guessed
47    /// from the *calling* thread would pair a start recorded on one stream
48    /// with an end recorded on another — which reads as a plausible duration
49    /// and means nothing.
50    #[serde(default)]
51    pub opened_on: u64,
52}
53
54/// Measure `func` in wall-clock time between two syncs of `device`.
55///
56/// What a backend with no device clock reports: the default of
57/// [`Backend::profile`](crate::Backend::profile), and what a backend that
58/// forwards its windows falls back to when the backend behind it opens none.
59/// The syncs are what make the number mean something, so unlike a device
60/// window this waits, and an inner window's syncs are charged to the outer.
61pub fn profile_system_time<B: crate::Backend, O: Send + 'static>(
62    device: &B::Device,
63    func: impl FnOnce() -> O + Send,
64) -> Result<(O, ProfileDuration), crate::ExecutionError> {
65    B::sync(device)?;
66    let start = Instant::now();
67    let out = func();
68    B::sync(device)?;
69    Ok((out, ProfileDuration::new_system_time(start, Instant::now())))
70}
71
72/// A closure-bracketed window over a backend's split
73/// [`profile_start`](crate::Backend::profile_start) / [`profile_end`](crate::Backend::profile_end),
74/// falling back to [`profile_system_time`] when the backend opens none.
75///
76/// What a forwarding backend measures with, when what it forwards to may or
77/// may not have a device clock, and what a backend with a device clock
78/// measures with when the closure must not run under a hold of the device.
79pub fn profile_with_tokens<B: crate::Backend, O: Send + 'static>(
80    device: &B::Device,
81    options: ProfileOptions,
82    func: impl FnOnce() -> O + Send,
83) -> Result<(O, ProfileDuration), ExecutionError> {
84    let opened = match B::profile_start(device) {
85        Ok(Some(token)) => token,
86        Ok(None) => return profile_system_time::<B, O>(device, func),
87        // The window could not be opened — a remote device reached from a
88        // browser thread, which cannot wait on the server. `func` still runs:
89        // a caller asked for their work to be measured, and handing back an
90        // error having quietly skipped the work is the one outcome they
91        // cannot recover from. Measuring is what failed, so only the
92        // measurement is lost.
93        Err(err) => {
94            func();
95            return Err(err);
96        }
97    };
98
99    // Held so an unwinding `func` abandons the window instead of leaving it
100    // open on the server for the rest of the process.
101    let mut window = OpenWindow::<B> {
102        device,
103        token: Some(opened),
104    };
105    let out = func();
106    let token = window
107        .token
108        .take()
109        .expect("the window is held from opening until here");
110    let duration = B::profile_end(device, token, options)?;
111    Ok((out, duration))
112}
113
114/// An open profiling window, abandoned on drop unless it was taken to be
115/// closed. See [`crate::Backend::profile_abandon`].
116struct OpenWindow<'a, B: crate::Backend> {
117    device: &'a B::Device,
118    token: Option<ProfileToken>,
119}
120
121impl<B: crate::Backend> Drop for OpenWindow<'_, B> {
122    fn drop(&mut self) {
123        if let Some(token) = self.token.take() {
124            B::profile_abandon(self.device, token);
125        }
126    }
127}
128
129pub(crate) fn profile_unsupported() -> ExecutionError {
130    ExecutionError::Generic {
131        reason: alloc::string::String::from(
132            "profiling windows are not supported by this backend; use `profile`",
133        ),
134        backtrace: BackTrace::capture(),
135    }
136}