OpenSWMM Engine  6.0.0-alpha.4
Data-oriented, plugin-extensible SWMM Engine (6.0.0-alpha.4)
Loading...
Searching...
No Matches
PerfTimers.hpp
Go to the documentation of this file.
1#pragma once
2// -----------------------------------------------------------------------------
3// PerfTimers — lightweight, env-gated wall-clock accumulators used to attribute
4// run time between the 2D surface solve, the 1D routing step, and the 2D-window
5// (rainfall + coupling) overhead. Header-only (C++17 inline variables) so no
6// CMake/link changes are needed; the ScopedTimer only touches a steady_clock at
7// coarse call sites (per macro-window / per routing step), never a hot inner
8// loop. The split is printed once from SWMMEngine::end() when OPENSWMM_PERF is
9// set. Zero cost when the env var is unset except the clock reads themselves.
10// -----------------------------------------------------------------------------
11#include <chrono>
12#include <cstdio>
13#include <cstdlib>
14
15namespace openswmm::perf {
16
17inline double sec_2d_window = 0.0; // full 2D advance window (rainfall+coupling+solve)
18inline double sec_2d_advance = 0.0; // pure 2D solve (solver_->advance)
19inline double sec_1d_step = 0.0; // 1D routing (router_.step)
20
21// ---------------------------------------------------------------------------
22// Load-phase accumulators — the open() / initialize() / start() window, which
23// is what the user sees between clicking Run and the first routing step. Same
24// env gate and same ScopedTimer as above; reset by open() so a process that
25// runs several models reports each one separately.
26// ---------------------------------------------------------------------------
27
28inline double sec_open_prescan2d = 0.0; // twoD::prescan2DUnitsHeader (whole-file pass)
29inline double sec_open_read = 0.0; // input_plugin->read (tokenize + handlers)
30// open.read split: the line scan (getline, trim, section_lines assembly) is
31// everything in read() that is not a section handler. Dispatch is measured and
32// scan is derived as the remainder, so the two always sum to open.read.
33inline double sec_read_dispatch = 0.0; // SectionRegistry handler execution
34inline double sec_open_resolve = 0.0; // input::resolve_cross_references
35inline double sec_open_validate = 0.0; // validate_project
36
37// resolve_cross_references sub-phases (sum <= sec_open_resolve; the remainder
38// is everything not individually bracketed).
39inline double sec_res_extfiles = 0.0; // FILE-backed timeseries/curve loads
40inline double sec_res_tables = 0.0; // gage/inflow/table name bindings
41inline double sec_res_transects = 0.0; // transect + street/inlet resolution
42inline double sec_res_xsect = 0.0; // per-link cross-section parameter loop
43inline double sec_res_shrink = 0.0; // shrink_all_to_fit
44
45inline double sec_init_state = 0.0; // initialize() body before init_modules()
46inline double sec_init_hydraulics = 0.0; // initHydraulics (router_.init, FV mesh build)
47inline double sec_init_hydrology = 0.0; // initHydrology
48inline double sec_init_quality = 0.0; // initQuality
49inline double sec_init_geometry = 0.0; // initGeometry
50
51inline double sec_start_iface = 0.0; // [FILES] interface-file open block
52inline double sec_start_plugins = 0.0; // plugins_.prepare_all (report preamble + .out header)
53
54// ---------------------------------------------------------------------------
55// FV 1D solver phase breakdown. `sec_1d_step` brackets the whole router step,
56// which is enough to say the FV solver is slow and nothing about WHY. These
57// split it by phase so a plan can be ranked against a profile instead of a
58// recollection.
59//
60// Granularity is per-substep-phase, never per-cell or per-face: at hundreds of
61// substeps a steady_clock read per phase is noise, at millions of faces it
62// would BE the profile. The counters are therefore incremented by loop extents
63// (`+= n`) at loop boundaries rather than by ++ inside the loop, which also
64// keeps them correct without atomics when the flux loop goes parallel.
65//
66// SERIAL-PATH ONLY. `n_fv_alg_*` are incremented inside solveAlgebraicNode,
67// which is serial today (plan Phase 3d proposes parallelizing it). If that
68// lands, these become per-thread accumulators or they become wrong.
69// ---------------------------------------------------------------------------
70
71inline double sec_fv_census = 0.0; // censusDt (Courant min-reduction)
72inline double sec_fv_flux = 0.0; // computeFluxes (active-face sweep)
73inline double sec_fv_nodesolve = 0.0; // relaxNodeFluxes + solveAlgebraicNode
74inline double sec_fv_positivity = 0.0; // limitPositivity
75inline double sec_fv_cellupdate = 0.0; // updateCells (incl. depth inversion)
76inline double sec_fv_nodeupdate = 0.0; // updateNodes
77inline double sec_fv_refreshdep = 0.0; // refreshDepths (full-mesh inversion)
78inline double sec_fv_savestate = 0.0; // saveState (11-vector snapshot)
79inline double sec_fv_restore = 0.0; // restoreState (snapshot + refreshDepths)
80inline double sec_fv_structref = 0.0; // refreshStructFlows (solver side)
81inline double sec_fv_bndcallback = 0.0; // Router::refreshFvBoundaryFlows (engine side)
82inline double sec_fv_rebuild = 0.0; // rebuildActiveLists (halo growth)
83inline double sec_fv_reconstruct = 0.0; // reconstructState (MUSCL slopes)
84inline double sec_fv_ltsfire = 0.0; // fireFaces (LTS face pass)
85inline double sec_fv_settle = 0.0; // settleAccumulators
86inline double sec_fv_tier = 0.0; // assignTiers
87
88inline long n_fv_substep = 0; // accepted substeps
89inline long n_fv_census = 0; // censusDt calls
90inline long n_fv_census_face = 0; // faces visited by all censuses
91inline long n_fv_invert = 0; // depthOfArea calls (by loop extent)
92inline long n_fv_savestate = 0; // saveState calls
93inline long n_fv_restore = 0; // ROLLBACKS — the rate saveState pays for
94inline long n_fv_structref = 0; // substep structure refreshes
95inline long n_fv_alg_visit = 0; // solveAlgebraicNode calls that got past the
96 // fixed-head early-out
97inline long n_fv_alg_passthru= 0; // ...of which took the degree-2 shortcut.
98 // passthru/visit is the fraction Phase 3c
99 // is trying to raise
100inline long n_fv_alg_solve = 0; // ...of which ran the root solve
101inline long n_fv_alg_resid = 0; // residual(h) evaluations inside them
102inline long n_fv_alg_flux = 0; // computeFaceFlux calls made from residuals
103
117
130inline void dump_fv() noexcept {
131 const double total = sec_fv_census + sec_fv_flux + sec_fv_nodesolve
136 + sec_fv_tier;
137 std::fprintf(stderr,
138 "[PERF-FV] step=%.4f total=%.4f "
139 "census=%.4f flux=%.4f nodesolve=%.4f positivity=%.4f "
140 "cellupdate=%.4f nodeupdate=%.4f refreshdepths=%.4f "
141 "savestate=%.4f restore=%.4f structrefresh=%.4f bndcallback=%.4f "
142 "rebuild=%.4f reconstruct=%.4f ltsfire=%.4f settle=%.4f tier=%.4f "
143 "n.substep=%ld n.census=%ld n.censusface=%ld n.invert=%ld "
144 "n.savestate=%ld n.rollback=%ld n.structrefresh=%ld "
145 "n.algvisit=%ld n.algpassthru=%ld n.algsolve=%ld "
146 "n.algresidual=%ld n.algfaceflux=%ld "
147 "passthru_frac=%.4f rollback_frac=%.4f "
148 "algresid_per_solve=%.2f algflux_per_solve=%.2f\n",
149 sec_1d_step, total,
159 (n_fv_alg_visit > 0)
160 ? static_cast<double>(n_fv_alg_passthru) / static_cast<double>(n_fv_alg_visit) : 0.0,
161 (n_fv_savestate > 0)
162 ? static_cast<double>(n_fv_restore) / static_cast<double>(n_fv_savestate) : 0.0,
163 (n_fv_alg_solve > 0)
164 ? static_cast<double>(n_fv_alg_resid) / static_cast<double>(n_fv_alg_solve) : 0.0,
165 (n_fv_alg_solve > 0)
166 ? static_cast<double>(n_fv_alg_flux) / static_cast<double>(n_fv_alg_solve) : 0.0);
167}
168
170inline std::chrono::steady_clock::time_point now() noexcept {
171 return std::chrono::steady_clock::now();
172}
173inline double since(std::chrono::steady_clock::time_point t0) noexcept {
174 return std::chrono::duration<double>(now() - t0).count();
175}
176
178inline bool enabled() noexcept {
179 static const bool on = (std::getenv("OPENSWMM_PERF") != nullptr);
180 return on;
181}
182
193
200inline void dump_load() noexcept {
201 const double open_total = sec_open_prescan2d + sec_open_read
203 const double init_total = sec_init_state + sec_init_hydraulics
206 const double start_total = sec_start_iface + sec_start_plugins;
207 std::fprintf(stderr,
208 "[PERF-LOAD] open=%.4f open.prescan2d=%.4f open.read=%.4f "
209 "read.scan=%.4f read.dispatch=%.4f "
210 "open.resolve=%.4f open.validate=%.4f "
211 "res.extfiles=%.4f res.tables=%.4f res.transects=%.4f res.xsect=%.4f "
212 "res.shrink=%.4f "
213 "init=%.4f init.state=%.4f init.hydraulics=%.4f init.hydrology=%.4f "
214 "init.quality=%.4f init.geometry=%.4f "
215 "start=%.4f start.iface=%.4f start.plugins=%.4f\n",
223 start_total, sec_start_iface, sec_start_plugins);
224}
225
226// Adds the elapsed wall time between construction and destruction to `acc`.
228 double* acc;
229 std::chrono::steady_clock::time_point t0;
230 explicit ScopedTimer(double& a) noexcept
231 : acc(&a), t0(std::chrono::steady_clock::now()) {}
232 ~ScopedTimer() noexcept {
233 *acc += std::chrono::duration<double>(
234 std::chrono::steady_clock::now() - t0).count();
235 }
236 ScopedTimer(const ScopedTimer&) = delete;
238};
239
270inline thread_local double nested_wall = 0.0;
271
273 double* acc = nullptr;
274 double outer = 0.0;
275 std::chrono::steady_clock::time_point t0;
276 explicit GatedTimer(double& a) noexcept {
277 if (enabled()) {
278 acc = &a;
279 outer = nested_wall; // stash whatever our parent has collected
280 nested_wall = 0.0; // ...and start a fresh tally for our own
281 t0 = std::chrono::steady_clock::now();
282 }
283 }
284 ~GatedTimer() noexcept {
285 if (!acc) return;
286 const double wall = std::chrono::duration<double>(
287 std::chrono::steady_clock::now() - t0).count();
288 // Ours is what the clock says minus what our children already claimed.
289 *acc += wall - nested_wall;
290 // Our FULL wall time is our parent's child time — the parent must not
291 // be charged for us twice, once through its own clock and once here.
292 nested_wall = outer + wall;
293 }
294 GatedTimer(const GatedTimer&) = delete;
295 GatedTimer& operator=(const GatedTimer&) = delete;
296};
297
300inline void count(long& c, long n = 1) noexcept {
301 if (enabled()) c += n;
302}
303
304} // namespace openswmm::perf
Definition PerfTimers.hpp:15
double sec_res_tables
Definition PerfTimers.hpp:40
double sec_open_read
Definition PerfTimers.hpp:29
thread_local double nested_wall
ScopedTimer that reads the clock only when OPENSWMM_PERF is set.
Definition PerfTimers.hpp:270
void reset_load() noexcept
Zeroes the load-phase accumulators. Called from open().
Definition PerfTimers.hpp:184
double sec_fv_cellupdate
Definition PerfTimers.hpp:75
double sec_init_hydrology
Definition PerfTimers.hpp:47
long n_fv_alg_visit
Definition PerfTimers.hpp:95
double sec_init_hydraulics
Definition PerfTimers.hpp:46
double sec_read_dispatch
Definition PerfTimers.hpp:33
double sec_fv_ltsfire
Definition PerfTimers.hpp:84
double sec_fv_nodesolve
Definition PerfTimers.hpp:73
double sec_fv_bndcallback
Definition PerfTimers.hpp:81
void count(long &c, long n=1) noexcept
Definition PerfTimers.hpp:300
double sec_2d_advance
Definition PerfTimers.hpp:18
long n_fv_alg_resid
Definition PerfTimers.hpp:101
long n_fv_invert
Definition PerfTimers.hpp:91
double sec_res_extfiles
Definition PerfTimers.hpp:39
bool enabled() noexcept
True when OPENSWMM_PERF is set. Cached — getenv is not free.
Definition PerfTimers.hpp:178
double sec_start_iface
Definition PerfTimers.hpp:51
void reset_fv() noexcept
Zeroes the FV phase accumulators. Called from Router::initFv.
Definition PerfTimers.hpp:105
double sec_fv_reconstruct
Definition PerfTimers.hpp:83
long n_fv_savestate
Definition PerfTimers.hpp:92
double sec_res_xsect
Definition PerfTimers.hpp:42
long n_fv_alg_solve
Definition PerfTimers.hpp:100
double sec_open_validate
Definition PerfTimers.hpp:35
double sec_fv_restore
Definition PerfTimers.hpp:79
double sec_init_geometry
Definition PerfTimers.hpp:49
double sec_fv_settle
Definition PerfTimers.hpp:85
long n_fv_structref
Definition PerfTimers.hpp:94
void dump_fv() noexcept
One machine-scrapeable line for the FV phase split.
Definition PerfTimers.hpp:130
std::chrono::steady_clock::time_point now() noexcept
Manual timing pair, for phases that do not fit a lexical scope.
Definition PerfTimers.hpp:170
double sec_fv_structref
Definition PerfTimers.hpp:80
long n_fv_alg_flux
Definition PerfTimers.hpp:102
double sec_start_plugins
Definition PerfTimers.hpp:52
double sec_fv_tier
Definition PerfTimers.hpp:86
double sec_res_shrink
Definition PerfTimers.hpp:43
double sec_2d_window
Definition PerfTimers.hpp:17
double sec_fv_flux
Definition PerfTimers.hpp:72
double sec_fv_positivity
Definition PerfTimers.hpp:74
double sec_open_prescan2d
Definition PerfTimers.hpp:28
void dump_load() noexcept
One machine-scrapeable line per phase on stderr.
Definition PerfTimers.hpp:200
double sec_fv_savestate
Definition PerfTimers.hpp:78
double sec_init_quality
Definition PerfTimers.hpp:48
long n_fv_substep
Definition PerfTimers.hpp:88
long n_fv_restore
Definition PerfTimers.hpp:93
double sec_res_transects
Definition PerfTimers.hpp:41
double sec_fv_refreshdep
Definition PerfTimers.hpp:77
double since(std::chrono::steady_clock::time_point t0) noexcept
Definition PerfTimers.hpp:173
double sec_fv_rebuild
Definition PerfTimers.hpp:82
long n_fv_census
Definition PerfTimers.hpp:89
double sec_fv_nodeupdate
Definition PerfTimers.hpp:76
double sec_fv_census
Definition PerfTimers.hpp:71
long n_fv_alg_passthru
Definition PerfTimers.hpp:97
long n_fv_census_face
Definition PerfTimers.hpp:90
double sec_open_resolve
Definition PerfTimers.hpp:34
double sec_1d_step
Definition PerfTimers.hpp:19
double sec_init_state
Definition PerfTimers.hpp:45
double outer
enclosing timer's tally, restored on close
Definition PerfTimers.hpp:274
GatedTimer & operator=(const GatedTimer &)=delete
double * acc
Definition PerfTimers.hpp:273
~GatedTimer() noexcept
Definition PerfTimers.hpp:284
std::chrono::steady_clock::time_point t0
Definition PerfTimers.hpp:275
GatedTimer(const GatedTimer &)=delete
GatedTimer(double &a) noexcept
Definition PerfTimers.hpp:276
ScopedTimer(const ScopedTimer &)=delete
ScopedTimer(double &a) noexcept
Definition PerfTimers.hpp:230
std::chrono::steady_clock::time_point t0
Definition PerfTimers.hpp:229
~ScopedTimer() noexcept
Definition PerfTimers.hpp:232
ScopedTimer & operator=(const ScopedTimer &)=delete
double * acc
Definition PerfTimers.hpp:228