Macro splashsurf_lib::profile
source · macro_rules! profile { ($name:expr) => { ... }; ($scope_id:ident, $name:expr) => { ... }; ($name:expr, parent = $parent_id:ident) => { ... }; ($scope_id:ident, $name:expr, parent = $parent_id:ident) => { ... }; }
profiling
only.Expand description
Creates a scope for profiling
This macro provides profiling inspired by the coarse-prof
crate.
The biggest difference to coarse-prof
is that it supports profiling in multi-threaded programs.
Note that this macro only works when the profiling
feature is enabled. Otherwise a dummy
implementation is provided with the same rules that expand to nothing. This allows the user to
disable this coarse grained profiling without modifying code depending on this macro. This can
be helpful to minimize overhead when using more elaborate profiling approaches.
Profiling works using scope guards that increment a thread local Profiler
(stored in the static PROFILER
variable) when they are dropped.
To evaluate the collected timings, the write
function can be used.
The write
function produces a human readable, hierarchically
structured overview of the gathered profiling data, like this:
frame: 100.00%, 10.40ms/call @ 96.17Hz
physics: 3.04%, 3.16ms/call @ 9.62Hz
collisions: 33.85%, 1.07ms/call @ 9.62Hz
render: 96.84%, 10.07ms/call @ 96.17Hz
It collects the timings over all threads and accumulates total runtime and number of calls for scopes at the same point in the call graph. As it is not easily possible to automatically connect the graph across thread boundaries, this is possible manually using one of the macro rules explained below.
The profile!
macro locally returns a scope guard tracking the time instant when it was invoked.
Note that this guard is stored in a variable called _profiling_scope_guard
which can result
in shadowing.
Note that even though it is safe to transfer a scope guard across threads, this does lead to
inconsistent timings when it is dropped. So this is most certainly not what you want to do.
Transferring ScopeId
s across thread boundaries however does make
sense when a to manually assign a parent scope across threads.
There are four ways to use this macro.
- A simple scope with a name. The nesting of this scope in the profiling hierarchy is inferred from the
scope that is surrounding the new scope on the current thread.
ⓘ
{ profile!("new scope") }
- A simple scope with a name and an id. An id of a new scope can be stored explicitly in a variable
named by the user (
scope_id
here). This can be used by other scopes to manually assign it a parent scope in the profiling hierarchy.ⓘ{ profile!(scope_id, "new scope") }
- A scope with a manually specified parent scope. As the profiling macros cannot automatically infer
the hierarchy across threads, it is possible to manually assign a scope to a parent scope.
ⓘ
{ profile!(scope_id, "outer scope") { vec.par_iter().for_each(|item| { profile!("inner scope", parent = scope_id); }); } }
- The second and third option can be combined:
ⓘ
profile!(inner_id, "inner scope", parent = outer_id);