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
9
10#pragma once
11// -----------------------------------------------------------------------------
12// PerfTimers — lightweight, env-gated wall-clock accumulators used to attribute
13// run time between the 2D surface solve, the 1D routing step, and the 2D-window
14// (rainfall + coupling) overhead. Header-only (C++17 inline variables) so no
15// CMake/link changes are needed; the ScopedTimer only touches a steady_clock at
16// coarse call sites (per macro-window / per routing step), never a hot inner
17// loop. The split is printed once from SWMMEngine::end() when OPENSWMM_PERF is
18// set. Zero cost when the env var is unset except the clock reads themselves.
19// -----------------------------------------------------------------------------
20#include <array>
21#include <mutex>
22#include <chrono>
23#include <cstdio>
24#include <cstdlib>
25
26namespace openswmm::perf {
27
28inline double sec_2d_window = 0.0; // full 2D advance window (rainfall+coupling+solve)
29inline double sec_2d_advance = 0.0; // pure 2D solve (solver_->advance)
30inline double sec_1d_step = 0.0; // 1D routing (router_.step)
31
32// ---------------------------------------------------------------------------
33// Load-phase accumulators — the open() / initialize() / start() window, which
34// is what the user sees between clicking Run and the first routing step. Same
35// env gate and same ScopedTimer as above; reset by open() so a process that
36// runs several models reports each one separately.
37// ---------------------------------------------------------------------------
38
39inline double sec_open_prescan2d = 0.0; // twoD::prescan2DUnitsHeader (whole-file pass)
40inline double sec_open_read = 0.0; // input_plugin->read (tokenize + handlers)
41// open.read split: the line scan (getline, trim, section_lines assembly) is
42// everything in read() that is not a section handler. Dispatch is measured and
43// scan is derived as the remainder, so the two always sum to open.read.
44inline double sec_read_dispatch = 0.0; // SectionRegistry handler execution
45inline double sec_open_resolve = 0.0; // input::resolve_cross_references
46inline double sec_open_validate = 0.0; // validate_project
47
48// resolve_cross_references sub-phases (sum <= sec_open_resolve; the remainder
49// is everything not individually bracketed).
50inline double sec_res_extfiles = 0.0; // FILE-backed timeseries/curve loads
51inline double sec_res_tables = 0.0; // gage/inflow/table name bindings
52inline double sec_res_transects = 0.0; // transect + street/inlet resolution
53inline double sec_res_xsect = 0.0; // per-link cross-section parameter loop
54inline double sec_res_shrink = 0.0; // shrink_all_to_fit
55
56inline double sec_init_state = 0.0; // initialize() body before init_modules()
57inline double sec_init_hydraulics = 0.0; // initHydraulics (router_.init, FV mesh build)
58inline double sec_init_hydrology = 0.0; // initHydrology
59inline double sec_init_quality = 0.0; // initQuality
60inline double sec_init_geometry = 0.0; // initGeometry
61
62inline double sec_start_iface = 0.0; // [FILES] interface-file open block
63inline double sec_start_plugins = 0.0; // plugins_.prepare_all (report preamble + .out header)
64
65// ---------------------------------------------------------------------------
66// FV 1D solver phase breakdown. `sec_1d_step` brackets the whole router step,
67// which is enough to say the FV solver is slow and nothing about WHY. These
68// split it by phase so a plan can be ranked against a profile instead of a
69// recollection.
70//
71// Granularity is per-substep-phase, never per-cell or per-face: at hundreds of
72// substeps a steady_clock read per phase is noise, at millions of faces it
73// would BE the profile. The counters are therefore incremented by loop extents
74// (`+= n`) at loop boundaries rather than by ++ inside the loop, which also
75// keeps them correct without atomics when the flux loop goes parallel.
76//
77// Parallel closure and node-solve counters are accumulated privately and
78// merged after each worksharing phase. Counts are exact at every team size.
79// ---------------------------------------------------------------------------
80
81inline double sec_fv_census = 0.0; // censusDt (Courant min-reduction)
82inline double sec_fv_flux = 0.0; // computeFluxes (active-face sweep)
83inline double sec_fv_nodesolve = 0.0; // relaxNodeFluxes + solveAlgebraicNode
84inline double sec_fv_positivity = 0.0; // limitPositivity
85inline double sec_fv_cellupdate = 0.0; // updateCells (incl. depth inversion)
86inline double sec_fv_nodeupdate = 0.0; // updateNodes
87inline double sec_fv_refreshdep = 0.0; // refreshDepths (full-mesh inversion)
88inline double sec_fv_savestate = 0.0; // saveState (11-vector snapshot)
89inline double sec_fv_restore = 0.0; // restoreState (snapshot + refreshDepths)
90inline double sec_fv_structref = 0.0; // refreshStructFlows (solver side)
91inline double sec_fv_bndcallback = 0.0; // Router::refreshFvBoundaryFlows (engine side)
92inline double sec_fv_rebuild = 0.0; // rebuildActiveLists (halo growth)
93inline double sec_fv_reconstruct = 0.0; // reconstructState (MUSCL slopes)
94inline double sec_fv_ltsfire = 0.0; // fireFaces (LTS face pass)
95inline double sec_fv_settle = 0.0; // settleAccumulators
96inline double sec_fv_tier = 0.0; // assignTiers
97
98// Each worker accumulates into a private CounterBatch. Only the completed
99// batch is merged, once per worker/phase; no atomics on individual operations.
121using FvCounts = std::array<long, n_fv_counter_count>;
123inline thread_local FvCounts* local_fv_counts = nullptr;
124inline std::mutex fv_counts_mutex;
125
126// Read/reset only between worksharing regions. Like the existing phase
127// timers, totals describe one profiled simulation at a time.
136inline long value(FvCounter counter) noexcept {
137 return fv_counts[counter];
138}
139
155
170inline void dump_fv() noexcept {
176 std::fprintf(
177 stderr,
178 "[PERF-FV] step=%.4f total=%.4f "
179 "census=%.4f flux=%.4f nodesolve=%.4f positivity=%.4f "
180 "cellupdate=%.4f nodeupdate=%.4f refreshdepths=%.4f "
181 "savestate=%.4f restore=%.4f structrefresh=%.4f bndcallback=%.4f "
182 "rebuild=%.4f reconstruct=%.4f ltsfire=%.4f settle=%.4f tier=%.4f "
183 "n.substep=%ld n.census=%ld n.censusface=%ld n.invert=%ld "
184 "n.savestate=%ld n.rollback=%ld n.structrefresh=%ld "
185 "n.algvisit=%ld n.algpassthru=%ld n.algsolve=%ld "
186 "n.algresidual=%ld n.algfaceflux=%ld "
187 "n.area=%ld n.width=%ld n.i1=%ld n.hydrad=%ld "
188 "n.macro=%ld n.macrorej=%ld "
189 "passthru_frac=%.4f rollback_frac=%.4f "
190 "algresid_per_solve=%.2f algflux_per_solve=%.2f\n",
200 (value(n_fv_alg_visit) > 0) ? static_cast<double>(value(n_fv_alg_passthru)) /
201 static_cast<double>(value(n_fv_alg_visit))
202 : 0.0,
203 (value(n_fv_savestate) > 0)
204 ? static_cast<double>(value(n_fv_restore)) / static_cast<double>(value(n_fv_savestate))
205 : 0.0,
206 (value(n_fv_alg_solve) > 0) ? static_cast<double>(value(n_fv_alg_resid)) /
207 static_cast<double>(value(n_fv_alg_solve))
208 : 0.0,
209 (value(n_fv_alg_solve) > 0)
210 ? static_cast<double>(value(n_fv_alg_flux)) / static_cast<double>(value(n_fv_alg_solve))
211 : 0.0);
212}
213
218inline std::chrono::steady_clock::time_point now() noexcept {
219 return std::chrono::steady_clock::now();
220}
221
227inline double since(std::chrono::steady_clock::time_point t0) noexcept {
228 return std::chrono::duration<double>(now() - t0).count();
229}
230
238inline bool enabled() noexcept {
239 static const bool on = (std::getenv("OPENSWMM_PERF") != nullptr);
240 return on;
241}
242
258
267inline void dump_load() noexcept {
268 const double open_total =
270 const double init_total = sec_init_state + sec_init_hydraulics + sec_init_hydrology +
272 const double start_total = sec_start_iface + sec_start_plugins;
273 std::fprintf(stderr,
274 "[PERF-LOAD] open=%.4f open.prescan2d=%.4f open.read=%.4f "
275 "read.scan=%.4f read.dispatch=%.4f "
276 "open.resolve=%.4f open.validate=%.4f "
277 "res.extfiles=%.4f res.tables=%.4f res.transects=%.4f res.xsect=%.4f "
278 "res.shrink=%.4f "
279 "init=%.4f init.state=%.4f init.hydraulics=%.4f init.hydrology=%.4f "
280 "init.quality=%.4f init.geometry=%.4f "
281 "start=%.4f start.iface=%.4f start.plugins=%.4f\n",
287}
288
289// Adds the elapsed wall time between construction and destruction to `acc`.
291 double* acc;
292 std::chrono::steady_clock::time_point t0;
302 explicit ScopedTimer(double& a) noexcept : acc(&a), t0(std::chrono::steady_clock::now()) {}
308 ~ScopedTimer() noexcept {
309 *acc += std::chrono::duration<double>(std::chrono::steady_clock::now() - t0).count();
310 }
311
317 ScopedTimer(const ScopedTimer&) = delete;
324};
325
356inline thread_local double nested_wall = 0.0;
357
359 double* acc = nullptr;
360 double outer = 0.0;
361 std::chrono::steady_clock::time_point t0;
372 explicit GatedTimer(double& a) noexcept {
373 if (enabled()) {
374 acc = &a;
375 outer = nested_wall; // stash whatever our parent has collected
376 nested_wall = 0.0; // ...and start a fresh tally for our own
377 t0 = std::chrono::steady_clock::now();
378 }
379 }
380
387 ~GatedTimer() noexcept {
388 if (!acc)
389 return;
390 const double wall =
391 std::chrono::duration<double>(std::chrono::steady_clock::now() - t0).count();
392 // Ours is what the clock says minus what our children already claimed.
393 *acc += wall - nested_wall;
394 // Our FULL wall time is our parent's child time — the parent must not
395 // be charged for us twice, once through its own clock and once here.
396 nested_wall = outer + wall;
397 }
398
404 GatedTimer(const GatedTimer&) = delete;
410 GatedTimer& operator=(const GatedTimer&) = delete;
411};
412
419 FvCounts local_;
420 FvCounts* previous_ = nullptr;
421 bool active_;
422
423public:
430 CounterBatch() noexcept : active_(enabled()) {
431 if (!active_)
432 return;
433 local_.fill(0);
434 previous_ = local_fv_counts;
435 local_fv_counts = &local_;
436 }
437
443 ~CounterBatch() noexcept {
444 if (!active_)
445 return;
446 local_fv_counts = previous_;
447 if (previous_) {
448 for (std::size_t i = 0; i < local_.size(); ++i)
449 (*previous_)[i] += local_[i];
450 } else {
451 const std::lock_guard<std::mutex> lock(fv_counts_mutex);
452 for (std::size_t i = 0; i < local_.size(); ++i)
453 fv_counts[i] += local_[i];
454 }
455 }
456
462 CounterBatch(const CounterBatch&) = delete;
469};
470
480inline void count(FvCounter counter, long n = 1) noexcept {
481 if (!enabled())
482 return;
483 if (local_fv_counts)
484 (*local_fv_counts)[counter] += n;
485 else
486 fv_counts[counter] += n;
487}
488
489} // namespace openswmm::perf
CounterBatch() noexcept
Install private FV counters for the current worker when profiling is enabled.
Definition PerfTimers.hpp:430
~CounterBatch() noexcept
Merge private counters into the enclosing batch or completed totals.
Definition PerfTimers.hpp:443
CounterBatch(const CounterBatch &)=delete
Prevent copying an active CounterBatch scope.
CounterBatch & operator=(const CounterBatch &)=delete
Prevent replacing an active CounterBatch scope by copy assignment.
Definition PerfTimers.hpp:26
double sec_res_tables
Definition PerfTimers.hpp:51
FvCounter
Definition PerfTimers.hpp:100
@ n_fv_geom_hydrad
Definition PerfTimers.hpp:116
@ n_fv_alg_visit
Definition PerfTimers.hpp:108
@ n_fv_substep
Definition PerfTimers.hpp:101
@ n_fv_macro_cycles
Definition PerfTimers.hpp:117
@ n_fv_savestate
Definition PerfTimers.hpp:105
@ n_fv_alg_passthru
Definition PerfTimers.hpp:109
@ n_fv_census_face
Definition PerfTimers.hpp:103
@ n_fv_counter_count
Definition PerfTimers.hpp:119
@ n_fv_alg_solve
Definition PerfTimers.hpp:110
@ n_fv_structref
Definition PerfTimers.hpp:107
@ n_fv_restore
Definition PerfTimers.hpp:106
@ n_fv_macro_rejected
Definition PerfTimers.hpp:118
@ n_fv_invert
Definition PerfTimers.hpp:104
@ n_fv_geom_i1
Definition PerfTimers.hpp:115
@ n_fv_alg_flux
Definition PerfTimers.hpp:112
@ n_fv_geom_area
Definition PerfTimers.hpp:113
@ n_fv_census
Definition PerfTimers.hpp:102
@ n_fv_geom_width
Definition PerfTimers.hpp:114
@ n_fv_alg_resid
Definition PerfTimers.hpp:111
long value(FvCounter counter) noexcept
Read one process-global completed FV counter total.
Definition PerfTimers.hpp:136
double sec_open_read
Definition PerfTimers.hpp:40
thread_local double nested_wall
ScopedTimer that reads the clock only when OPENSWMM_PERF is set.
Definition PerfTimers.hpp:356
void reset_load() noexcept
Zero load/open/initialize/start phase accumulators.
Definition PerfTimers.hpp:249
double sec_fv_cellupdate
Definition PerfTimers.hpp:85
double sec_init_hydrology
Definition PerfTimers.hpp:58
double sec_init_hydraulics
Definition PerfTimers.hpp:57
double sec_read_dispatch
Definition PerfTimers.hpp:44
double sec_fv_ltsfire
Definition PerfTimers.hpp:94
double sec_fv_nodesolve
Definition PerfTimers.hpp:83
double sec_fv_bndcallback
Definition PerfTimers.hpp:91
double sec_2d_advance
Definition PerfTimers.hpp:29
double sec_res_extfiles
Definition PerfTimers.hpp:50
bool enabled() noexcept
Read the process-cached profiling environment gate.
Definition PerfTimers.hpp:238
double sec_start_iface
Definition PerfTimers.hpp:62
void reset_fv() noexcept
Zero FV phase timers and completed counter totals.
Definition PerfTimers.hpp:146
double sec_fv_reconstruct
Definition PerfTimers.hpp:93
thread_local FvCounts * local_fv_counts
Definition PerfTimers.hpp:123
double sec_res_xsect
Definition PerfTimers.hpp:53
std::array< long, n_fv_counter_count > FvCounts
Definition PerfTimers.hpp:121
double sec_open_validate
Definition PerfTimers.hpp:46
double sec_fv_restore
Definition PerfTimers.hpp:89
double sec_init_geometry
Definition PerfTimers.hpp:60
double sec_fv_settle
Definition PerfTimers.hpp:95
void dump_fv() noexcept
One machine-scrapeable line for the FV phase split.
Definition PerfTimers.hpp:170
std::chrono::steady_clock::time_point now() noexcept
Sample the steady monotonic clock regardless of profiling enablement.
Definition PerfTimers.hpp:218
std::mutex fv_counts_mutex
Definition PerfTimers.hpp:124
double sec_fv_structref
Definition PerfTimers.hpp:90
double sec_start_plugins
Definition PerfTimers.hpp:63
double sec_fv_tier
Definition PerfTimers.hpp:96
double sec_res_shrink
Definition PerfTimers.hpp:54
double sec_2d_window
Definition PerfTimers.hpp:28
double sec_fv_flux
Definition PerfTimers.hpp:82
double sec_fv_positivity
Definition PerfTimers.hpp:84
double sec_open_prescan2d
Definition PerfTimers.hpp:39
void dump_load() noexcept
One machine-scrapeable line per phase on stderr.
Definition PerfTimers.hpp:267
@ count
Definition ProcessPerf.hpp:26
double sec_fv_savestate
Definition PerfTimers.hpp:88
double sec_init_quality
Definition PerfTimers.hpp:59
double sec_res_transects
Definition PerfTimers.hpp:52
double sec_fv_refreshdep
Definition PerfTimers.hpp:87
double since(std::chrono::steady_clock::time_point t0) noexcept
Measure elapsed wall time from a steady-clock sample.
Definition PerfTimers.hpp:227
double sec_fv_rebuild
Definition PerfTimers.hpp:92
FvCounts fv_counts
Definition PerfTimers.hpp:122
double sec_fv_nodeupdate
Definition PerfTimers.hpp:86
double sec_fv_census
Definition PerfTimers.hpp:81
double sec_open_resolve
Definition PerfTimers.hpp:45
double sec_1d_step
Definition PerfTimers.hpp:30
double sec_init_state
Definition PerfTimers.hpp:56
double outer
enclosing timer's tally, restored on close
Definition PerfTimers.hpp:360
GatedTimer & operator=(const GatedTimer &)=delete
Prevent replacing an active GatedTimer scope by copy assignment.
double * acc
Definition PerfTimers.hpp:359
~GatedTimer() noexcept
Add elapsed wall time excluding nested GatedTimer intervals.
Definition PerfTimers.hpp:387
std::chrono::steady_clock::time_point t0
Definition PerfTimers.hpp:361
GatedTimer(const GatedTimer &)=delete
Prevent copying an active GatedTimer scope.
GatedTimer(double &a) noexcept
Start a profiling-gated self-time interval on the current thread.
Definition PerfTimers.hpp:372
ScopedTimer(const ScopedTimer &)=delete
Prevent copying an active ScopedTimer scope.
ScopedTimer(double &a) noexcept
Start an unconditional wall timer against a borrowed accumulator.
Definition PerfTimers.hpp:302
std::chrono::steady_clock::time_point t0
Definition PerfTimers.hpp:292
~ScopedTimer() noexcept
Add complete elapsed wall seconds to the borrowed accumulator.
Definition PerfTimers.hpp:308
ScopedTimer & operator=(const ScopedTimer &)=delete
Prevent replacing an active ScopedTimer scope by copy assignment.
double * acc
Definition PerfTimers.hpp:291