KaMPIng 0.1.1
Flexible and (near) zero-overhead C++ bindings for MPI
Loading...
Searching...
No Matches
timer.hpp
Go to the documentation of this file.
1// This file is part of KaMPIng.
2//
3// Copyright 2023 The KaMPIng Authors
4//
5// KaMPIng is free software : you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
6// License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
7// version. KaMPIng is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
8// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
9// for more details.
10//
11// You should have received a copy of the GNU Lesser General Public License along with KaMPIng. If not, see
12// <https://www.gnu.org/licenses/>.
13
14/// @file
15/// This file contains a (distributed) timer class.
16
17#pragma once
18
19#include <kassert/kassert.hpp>
20#include <mpi.h>
21
22#include "kamping/collectives/barrier.hpp"
23#include "kamping/collectives/gather.hpp"
24#include "kamping/communicator.hpp"
28
29namespace kamping::measurements {
30
31/// @brief Distributed timer object.
32///
33/// ## Timer hierarchy: ##
34/// The timer is able to execute time measurements in a hierarchical manner.
35/// Time measurements are executed with a matching pair of calls to Timer::start() and Timer::stop().
36/// Each call to start() enters a new level in the hierarchy which is left again with the corresponding call to
37/// stop(). Time measurements can be nested and the parent time measurement remains active while its child time
38/// measurement take place.
39///
40/// See the following pseudocode example:
41///
42/// \code
43/// Timer timer;
44/// timer.start("algorithm");
45/// timer.start("preprocessing");
46/// timer.stop(); // stops "preprocessing" measurement
47/// timer.start("core_algorithm");
48/// timer.start("subroutine");
49/// timer.stop(); // stops "subroutine" measurement
50/// timer.stop(); // stops "core_algorithm" measurement
51/// timer.start("postprocessing");
52/// timer.stop(); // stops "postprocessing" measurement
53/// timer.stop(); // stops "algorithm" measurement
54///
55/// timer.aggregate_and_print(Printer{}); // aggregates measurements across all participating ranks and print results
56/// \endcode
57///
58/// This corresponds to the following timing hierarchy:
59///
60/// \code
61/// // Measurement key Duration
62/// // ----------------------------------
63/// // algorithm:...............6.0 sec
64/// // |-- preprocessing:.......1.0 sec
65/// // |-- core_algorithm:......4.0 sec
66/// // | `-- subroutine:......2.0 sec
67/// // `-- postprocessing:......2.0 sec
68/// \endcode
69///
70/// ## Aggregation operations ##
71///
72/// There are two types of aggregation operations:
73///
74/// 1) Local aggregation operations - It is possible to execute measurements with the same key multiple times. Local
75/// aggregation operations specify how these repeated time measurements will be stored. Currently, there are two
76/// options - stop_and_add() and stop_and_append(). See the following examples:
77///
78/// \code
79/// timer.start("foo");
80/// timer.stop_and_add();
81/// timer.start("foo");
82/// timer.stop_and_add();
83/// \endcode
84/// The result of this program is one stored duration for the key "foo" which is the sum of the durations for the two
85/// measurements with key "foo".
86///
87/// The other option to handle repeated measurements with the same key is stop_and_append():
88/// \code
89/// timer.start("bar");
90/// timer.stop_and_append();
91/// timer.start("bar");
92/// timer.stop_and_append();
93/// \endcode
94///
95/// This program results in a list with two durations for the key "bar". We call the number of durations
96/// stored for a key its \c dimension.
97///
98/// 2) Global (communicator-wide) aggregation operations specify how the stored duration(s) for a specific measurement
99/// key shall be aggregated. The default operation (if no operations are specified via the stop() method) is to take the
100/// maximum duration over all ranks in the communicator. If there are multiple durations for a certain key, the
101/// aggregation operation is applied element-wise and the result remains a list with the same size as the number of
102/// input durations. The communicator-wide aggregation operation are applied in the evaluation phase started with
103/// calls to aggregate() or aggregate_and_print().
104///
105///
106/// The timer hierarchy that is implicitly created by start() and stop() must be the same on all ranks in the given
107/// communicator. The number of time measurements with a specific key may vary as long as the number of stored duration
108/// are the same:
109/// Consider for example a communicator of size two and rank 0 and 1 both measure the time of a function \c
110/// foo() each time it is called using the key \c "computation". This is only valid if either each rank calls foo()
111/// exactly the same number of times and local aggregation happens using the mode \c append, or if the measured time is
112/// locally aggregated using add, i.e. the value(s) stored for a single key at the same hierarchy level must have
113/// matching dimensions.
114/// Furthermore, global communicator-wide duration aggregation operations specified on ranks other than the root rank
115/// are ignored.
116///
117///
118/// @see The usage example for Timer provides some more information on how the class can be used.
119///
120/// @tparam CommunicatorType Communicator in which the time measurements are executed.
121template <typename CommunicatorType = Communicator<>>
122class Timer {
123public:
124 using Duration = double; ///< Type of durations. @todo make this customizable.
125 /// @brief Constructs a timer using the \c MPI_COMM_WORLD communicator.
126 Timer() : Timer{comm_world()} {}
127
128 /// @brief Constructs a timer using a given communicator.
129 ///
130 /// @param comm Communicator in which the time measurements are executed.
131 Timer(CommunicatorType const& comm) : _timer_tree{}, _comm{comm}, _is_timer_enabled{true} {}
132
133 /// @brief Synchronizes all ranks in the underlying communicator via a barrier and then start the measurement with
134 /// the given key.
135 /// @param key Key with which the started time measurement is associated. Note that the user is responsible for
136 /// providing keys, which are valid in the used output format during printing.
137 void synchronize_and_start(std::string const& key) {
138 bool const use_barrier = true;
139 start_impl(key, use_barrier);
140 }
141
142 /// @brief Starts the measurement with the given key.
143 /// @param key Key with which the started time measurement is associated. Note that the user is responsible for
144 /// providing keys, which are valid in the used output format during printing.
145 void start(std::string const& key) {
146 bool const use_barrier = false;
147 start_impl(key, use_barrier);
148 }
149
150 /// @brief Stops the currently active measurement and stores the result.
151 /// @todo Add a toggle option to the Timer class to set the default key aggregation behaviour for stop().
152 /// @todo Replace the global_aggregation_modes parameter with a more efficient and ergonomic solution using
153 /// variadic templates.
154 /// @param global_aggregation_modes Specifies how the measurement duration is aggregated over all participating
155 /// ranks when aggregate() is called.
156 void
157 stop(std::vector<GlobalAggregationMode> const& global_aggregation_modes = std::vector<GlobalAggregationMode>{}) {
158 stop_impl(LocalAggregationMode::accumulate, global_aggregation_modes);
159 }
160
161 /// @brief Stops the currently active measurement and stores the result. If the key associated with the
162 /// measurement that is stopped has already been used at the current hierarchy level the duration is added to
163 /// the last measured duration at this hierarchy level with the same key.
164 /// @param global_aggregation_modes Specifies how the measurement duration is aggregated over all participating
165 /// ranks when aggregate() is called.
167 std::vector<GlobalAggregationMode> const& global_aggregation_modes = std::vector<GlobalAggregationMode>{}
168 ) {
169 stop_impl(LocalAggregationMode::accumulate, global_aggregation_modes);
170 }
171
172 /// @brief Stops the currently active measurement and stores the result. If the key associated with the
173 /// measurement that is stopped has already been used at the current hierarchy level the duration is appended to a
174 /// list of previously measured durations at this hierarchy level with the same key.
175 /// @param global_aggregation_modes Specifies how the measurement duration is aggregated over all participating
176 /// ranks when aggregate() is called.
178 std::vector<GlobalAggregationMode> const& global_aggregation_modes = std::vector<GlobalAggregationMode>{}
179 ) {
180 stop_impl(LocalAggregationMode::append, global_aggregation_modes);
181 }
182
183 /// @brief Evaluates the time measurements represented by the timer tree for which the current node is the root
184 /// node globally. The measured durations are aggregated over all participating ranks and the result is stored at
185 /// the root rank of the given communicator. The used aggregation operations can be specified via
186 /// TimerTreeNode::data_aggregation_operations().
187 /// The durations are aggregated node by node.
188 ///
189 /// @return AggregatedTree object which encapsulated the aggregated data in a tree structure representing the
190 /// measurements.
191 auto aggregate() {
192 AggregatedTree<Duration> aggregated_tree(_timer_tree.root, _comm);
193 return aggregated_tree;
194 }
195
196 /// @brief Clears all stored measurements.
197 void clear() {
198 _timer_tree.reset();
199 }
200
201 /// @brief (Re-)Enable start/stop operations.
202 void enable() {
203 _is_timer_enabled = true;
204 }
205 /// @brief Disable start/stop operations, i.e., start()/stop() operations do not have any effect.
206 void disable() {
207 _is_timer_enabled = false;
208 }
209
210 /// @brief Aggregates and outputs the executed measurements. The output is done via the print()
211 /// method of a given Printer object.
212 ///
213 /// The print() method must accept an object of type AggregatedTreeNode and receives the root of the evaluated timer
214 /// tree as parameter. The print() method is only called on the root rank of the communicator. See
215 /// AggregatedTreeNode for the accessible data. The AggregatedTreeNode::children() member function can be used to
216 /// navigate the nested measurement structure.
217 ///
218 /// @tparam Printer Type of printer which is used to output the aggregated timing data. Printer must possess a
219 /// member print() which accepts a AggregatedTreeNode as parameter.
220 /// @param printer Printer object used to output the aggregated timing data.
221 template <typename Printer>
223 auto const aggregated_tree = aggregate();
224 if (_comm.is_root()) {
225 printer.print(aggregated_tree.root());
226 }
227 }
228
229private:
231 _timer_tree; ///< Timer tree used to represent the hierarchical time measurements.
232 CommunicatorType const& _comm; ///< Communicator in which the time measurements take place.
233 bool _is_timer_enabled; ///< Flag indicating whether start/stop operations are enabled.
234
235 /// @brief Starts a time measurement.
236 void start_impl(std::string const& key, bool use_barrier) {
237 if (!_is_timer_enabled) {
238 return;
239 }
240 auto& node = _timer_tree.current_node->find_or_insert(key);
241 node.is_active(true);
242 if (use_barrier) {
243 _comm.barrier();
244 }
245 auto start_point = Environment<>::wtime();
246 node.startpoint(start_point);
247 _timer_tree.current_node = &node;
248 }
249
250 /// @brief Stops the currently active measurement and stores the result.
251 /// @param local_aggregation_mode Specifies how the measurement duration is locally aggregated when there are
252 /// multiple measurements at the same level with identical key.
253 /// @param global_aggregation_modes Specifies how the measurement duration is aggregated over all participating
254 /// ranks when aggregate() is called.
255 void stop_impl(
256 LocalAggregationMode local_aggregation_mode, std::vector<GlobalAggregationMode> const& global_aggregation_modes
257 ) {
258 if (!_is_timer_enabled) {
259 return;
260 }
261 auto endpoint = Environment<>::wtime();
262 KASSERT(
263 _timer_tree.current_node->is_active(),
264 "There is no corresponding call to start() associated with this call to stop()",
266 );
267 _timer_tree.current_node->is_active(false);
268 auto startpoint = _timer_tree.current_node->startpoint();
269 _timer_tree.current_node->aggregate_measurements_locally(endpoint - startpoint, local_aggregation_mode);
270 if (!global_aggregation_modes.empty()) {
271 _timer_tree.current_node->measurements_aggregation_operations() = global_aggregation_modes;
272 }
273 _timer_tree.current_node = _timer_tree.current_node->parent_ptr();
274 }
275};
276
277/// @brief Gets a reference to a kamping::measurements::BasicTimer.
278///
279/// @return A reference to a kamping::measurements::BasicCommunicator.
282 return timer;
283}
284
285} // namespace kamping::measurements
static double wtime()
Returns the elapsed time since an arbitrary time in the past.
Definition environment.hpp:158
STL-compatible allocator for requesting memory using the builtin MPI allocator.
Definition allocator.hpp:32
Distributed timer object.
Definition timer.hpp:122
void aggregate_and_print(Printer &&printer)
Aggregates and outputs the executed measurements. The output is done via the print() method of a give...
Definition timer.hpp:222
void stop_and_append(std::vector< GlobalAggregationMode > const &global_aggregation_modes=std::vector< GlobalAggregationMode >{})
Stops the currently active measurement and stores the result. If the key associated with the measurem...
Definition timer.hpp:177
void disable()
Disable start/stop operations, i.e., start()/stop() operations do not have any effect.
Definition timer.hpp:206
void start(std::string const &key)
Starts the measurement with the given key.
Definition timer.hpp:145
void clear()
Clears all stored measurements.
Definition timer.hpp:197
void enable()
(Re-)Enable start/stop operations.
Definition timer.hpp:202
Timer(CommunicatorType const &comm)
Constructs a timer using a given communicator.
Definition timer.hpp:131
void stop(std::vector< GlobalAggregationMode > const &global_aggregation_modes=std::vector< GlobalAggregationMode >{})
Stops the currently active measurement and stores the result.
Definition timer.hpp:157
Timer()
Constructs a timer using the MPI_COMM_WORLD communicator.
Definition timer.hpp:126
void stop_and_add(std::vector< GlobalAggregationMode > const &global_aggregation_modes=std::vector< GlobalAggregationMode >{})
Stops the currently active measurement and stores the result. If the key associated with the measurem...
Definition timer.hpp:166
void synchronize_and_start(std::string const &key)
Synchronizes all ranks in the underlying communicator via a barrier and then start the measurement wi...
Definition timer.hpp:137
double Duration
Definition timer.hpp:124
auto aggregate()
Evaluates the time measurements represented by the timer tree for which the current node is the root ...
Definition timer.hpp:191
Wrapper for MPI functions that don't require a communicator.
constexpr int light
Assertion level for lightweight assertions.
Definition assertion_levels.hpp:13
Tree consisting of objects of type NodeType. The tree constitutes a hierarchy of measurements such th...
Definition measurement_utils.hpp:278
NodeType * current_node
Pointer to the currently active node of the tree.
Definition measurement_utils.hpp:289
Timer< Communicator<> > & timer()
Gets a reference to a kamping::measurements::BasicTimer.
Definition timer.hpp:280