Skip to content

Commit c97abeb

Browse files
reneSchmHenrZu
andauthored
17 Timing framework (#1179)
- Update to C++20 - Add performance timers to memilio. - When used via AutoTimer, they are thread safe their results are automatically collected and evaluated. - Other classes added are BasicTimer, NamedTimer, TimerRegistrar, Table-/List-/Printer. - Add a class StringLiteral that allows using string literals at compile time. - Add functions for getting OpenMP thread id and current or max. number of threads. Co-authored-by: Henrik Zunker <69154294+HenrZu@users.noreply.github.com>
1 parent c7f439d commit c97abeb

34 files changed

+1925
-44
lines changed

cpp/CMakeLists.txt

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ cmake_minimum_required(VERSION 3.13)
22

33
project(memilio VERSION 1.0.0)
44

5+
set(CMAKE_CXX_STANDARD "20")
6+
set(CMAKE_CXX_STANDARD_REQUIRED "20")
7+
58
option(MEMILIO_BUILD_TESTS "Build memilio unit tests." ON)
69
option(MEMILIO_BUILD_EXAMPLES "Build memilio examples." ON)
710
option(MEMILIO_BUILD_MODELS "Build memilio models." ON)
@@ -126,15 +129,29 @@ if(MEMILIO_ENABLE_WARNINGS)
126129
"-Wall;-Wextra;-Wshadow;--pedantic;")
127130
endif()
128131
endif()
129-
132+
# exclude some warnings we accept
130133
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
131134
string(APPEND MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS
132-
"-Wno-unknown-warning;-Wno-pragmas;-Wno-deprecated-copy;-Wno-expansion-to-defined;-Wno-stringop-overread;")
135+
"-Wno-unknown-warning;-Wno-pragmas;-Wno-deprecated-copy;-Wno-expansion-to-defined;")
133136
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
134137
string(APPEND MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS
135-
"-Wno-unknown-warning-option;-Wno-deprecated;-Wno-gnu-zero-variadic-macro-arguments;-Wno-stringop-overread;")
138+
"-Wno-unknown-warning-option;-Wno-deprecated;-Wno-gnu-zero-variadic-macro-arguments;")
136139
endif()
137-
140+
# woyrkarounds for compiler bugs or overzealous optimization/analysis
141+
if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RELEASE")
142+
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
143+
string(APPEND MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS
144+
"-Wno-restrict;" # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105329
145+
"-Wno-maybe-uninitialized;" # https://gcc.gnu.org/bugzilla/buglist.cgi?quicksearch=may%20be%20uninitialized
146+
"-Wno-stringop-overread;"
147+
)
148+
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
149+
string(APPEND MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS
150+
"-Wno-stringop-overread;"
151+
)
152+
endif()
153+
endif()
154+
# finalize string by setting warnings as errors
138155
if(MEMILIO_ENABLE_WARNINGS_AS_ERRORS)
139156
if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
140157
string(APPEND MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS

cpp/examples/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ add_executable(euler_example euler_test.cpp)
66
target_link_libraries(euler_example PRIVATE memilio)
77
target_compile_options(euler_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS})
88

9+
add_executable(performance_timer_example performance_timers.cpp)
10+
target_link_libraries(performance_timer_example PRIVATE memilio)
11+
target_compile_options(performance_timer_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS})
12+
913
add_executable(ode_secir_parameter_sampling_example ode_secir_parameter_sampling.cpp)
1014
target_link_libraries(ode_secir_parameter_sampling_example PRIVATE memilio ode_secir)
1115
target_compile_options(ode_secir_parameter_sampling_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS})
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright (C) 2020-2025 MEmilio
3+
*
4+
* Authors: Rene Schmieding
5+
*
6+
* Contact: Martin J. Kuehn <Martin.Kuehn@DLR.de>
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
#include "memilio/timer/auto_timer.h"
21+
#include "memilio/timer/table_printer.h"
22+
23+
#include <thread> // This is only used for the example load function.
24+
25+
/// @brief Workload for the timers to report on.
26+
void load()
27+
{
28+
// Two very simple work loads to test the timers. Choose one, an comment out the other.
29+
30+
// Option a: increment a counter. Extremely cheap load, so for large N, the timing overhead will matter.
31+
// Uses volatile in an attempt to avoid compiler optimisations.
32+
33+
// volatile static int ctr = 0;
34+
// ++ctr;
35+
36+
// Option b: let the current thread briefly wait. The timing overhead should be negligible.
37+
38+
std::this_thread::sleep_for(std::chrono::milliseconds(3));
39+
}
40+
41+
int main()
42+
{
43+
// Specify the namespace of AutoTimer, so we don't have to repeat it. Do not do this with entire namespaces.
44+
using mio::timing::AutoTimer;
45+
46+
// Start immediately timing the main function. An AutoTimer starts timing upon its creation, and ends timing when
47+
// it is destroyed. This usually happens when a function returns, or a scope indicated by {curly braces} ends.
48+
// The name of the AutoTimer object (here timer_ms) does not matter, but the name given to it as a template
49+
// (i.e. "main") does, as it is used to identify the NamedTimer that is used internally, it works like a map key.
50+
AutoTimer<"main"> timer_ms;
51+
52+
// Set a Printer to output the timers.
53+
// These only have to be set if you want to customize the output. By default, TimerRegistrar prints all timers at
54+
// the end of the programm. This can be disabled by calling `TimerRegistrar::disable_final_timer_summary()`.
55+
auto printer = std::make_unique<mio::timing::TablePrinter>();
56+
printer->set_time_format("{:e}");
57+
mio::timing::TimerRegistrar::get_instance().set_printer(std::move(printer));
58+
59+
// To manually print all timers, use `TimerRegistrar::print_timers()`, but make sure that no timers are running.
60+
// The "main" timer in this example would make that difficult, but you can simply add another scope around it,
61+
// similar to the "compute loops" timer below.
62+
63+
const int N = 1000; // Number of iterations. Be wary of increasing this number when using the sleep_for load!
64+
65+
std::cout << "Num threads: " << mio::omp::get_max_threads() << "\n";
66+
67+
// Open a new scope to time computations.
68+
{
69+
// This is another a good example of how to use AutoTimer, now inside an unnamed scope.
70+
AutoTimer<"compute loops"> timer_cs;
71+
72+
// First compute loop.
73+
{
74+
// With the second string we define the scope of the timer. Here it is kind of misused, the scope should
75+
// usually name the surrounding class, struct, or function. This has two reasons: First, it makes name
76+
// collisions less likely. Second, the scope can be used to analyze the results. Hence, "first loop" would
77+
// be way too granular, the appropriate scope in this case would be "main", if any.
78+
AutoTimer<"compute", "first loop"> timer_out;
79+
80+
PRAGMA_OMP(parallel for)
81+
for (int i = 0; i < N; i++) {
82+
load();
83+
}
84+
}
85+
// Second compute loop, with an additional timer during computation. While the timing overhead is minimal, it is
86+
// still measurable, so it should be generally avoided to time inside main compute loops.
87+
{
88+
// Again, we use a not quite optimal name for the scope, but it is shared with the inner timer. In this
89+
// example, it would be enough to include "second loop" in the name. The scope should be used for
90+
// differentiating between, e.g., different models or simulations.
91+
AutoTimer<"compute (with inner timer)", "second loop"> timer_out;
92+
93+
PRAGMA_OMP(parallel for)
94+
for (int i = 0; i < N; i++) {
95+
// Note that when using multithreading, this timer will measure a longer (aggregated) time than its
96+
// outer timer.
97+
AutoTimer<"inner timer", "second loop"> timer_in;
98+
load();
99+
}
100+
}
101+
}
102+
103+
return 0;
104+
}

cpp/memilio/CMakeLists.txt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,15 @@ add_library(memilio
7070
mobility/graph_simulation.cpp
7171
mobility/graph.h
7272
mobility/graph.cpp
73+
timer/auto_timer.h
74+
timer/basic_timer.cpp
75+
timer/basic_timer.h
76+
timer/definitions.cpp
77+
timer/definitions.h
78+
timer/list_printer.h
79+
timer/named_timer.h
80+
timer/registration.h
81+
timer/timer_registrar.h
7382
utils/visitor.h
7483
utils/uncertain_value.h
7584
utils/uncertain_value.cpp
@@ -95,6 +104,8 @@ add_library(memilio
95104
utils/miompi.h
96105
utils/miompi.cpp
97106
utils/mioomp.h
107+
utils/mioomp.cpp
108+
utils/string_literal.h
98109
utils/type_list.h
99110
)
100111

@@ -104,7 +115,6 @@ target_include_directories(memilio PUBLIC
104115
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
105116
)
106117

107-
target_compile_features(memilio PUBLIC cxx_std_17)
108118
target_link_libraries(memilio PUBLIC spdlog::spdlog Eigen3::Eigen Boost::boost Boost::filesystem Boost::disable_autolinking Random123 AD::AD)
109119
target_compile_options(memilio
110120
PRIVATE

cpp/memilio/timer/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# MEmilio C++ timing framework
2+
3+
This directory contains MEmilio's performance timers.
4+
5+
To get started, check out the
6+
[official documentation](https://memilio.readthedocs.io/en/latest/cpp/performance_timers.html), or the
7+
[code example](../../examples/performance_timers.cpp).

cpp/memilio/timer/auto_timer.h

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright (C) 2020-2025 MEmilio
3+
*
4+
* Authors: Rene Schmieding
5+
*
6+
* Contact: Martin J. Kuehn <Martin.Kuehn@DLR.de>
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
#ifndef MIO_TIMER_AUTO_TIMER_H
21+
#define MIO_TIMER_AUTO_TIMER_H
22+
23+
#include "memilio/timer/basic_timer.h"
24+
#include "memilio/timer/named_timer.h"
25+
#include "memilio/utils/string_literal.h"
26+
27+
namespace mio
28+
{
29+
namespace timing
30+
{
31+
32+
/**
33+
* @brief Timer that automatically starts when it is created, and stops when it is destroyed.
34+
* @tparam Name, Scope The name and scope of a NamedTimer. Do not set these if you want to use a BasicTimer.
35+
*/
36+
template <StringLiteral Name, StringLiteral Scope = "">
37+
class AutoTimer
38+
{
39+
public:
40+
/// @brief Run the NamedTimer given by the template parameter(s) Name (and Scope).
41+
AutoTimer()
42+
: m_timer(NamedTimer<Name, Scope>::get_instance())
43+
{
44+
m_timer.start();
45+
}
46+
47+
/// @brief Run the given BasicTimer. Does not take ownership, so mind the timer's lifetime!
48+
AutoTimer(BasicTimer& timer)
49+
: m_timer(timer)
50+
{
51+
static_assert(Name.empty() && Scope.empty(),
52+
"Do not set the Name and Scope templates when using this constructor.");
53+
m_timer.start();
54+
}
55+
56+
AutoTimer(AutoTimer&) = delete;
57+
AutoTimer(AutoTimer&&) = delete;
58+
59+
~AutoTimer()
60+
{
61+
m_timer.stop();
62+
}
63+
64+
private:
65+
BasicTimer& m_timer; ///< Reference to the timer so it can be stopped in AutoTimer's destructor.
66+
};
67+
68+
// Deduction guide that allows omitting the template parameter when using the BasicTimer constructor.
69+
AutoTimer(BasicTimer& timer) -> AutoTimer<"">;
70+
71+
} // namespace timing
72+
} // namespace mio
73+
74+
#endif // MIO_TIMER_AUTO_TIMER_H

cpp/memilio/timer/basic_timer.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#include "memilio/timer/basic_timer.h"
2+
#include "memilio/utils/logging.h"
3+
4+
namespace mio
5+
{
6+
namespace timing
7+
{
8+
9+
void BasicTimer::set_running(bool new_state)
10+
{
11+
#ifndef NDEBUG
12+
m_is_running = new_state;
13+
#else
14+
mio::unused(new_state);
15+
#endif
16+
}
17+
18+
void BasicTimer::should_be_running(bool expected, const std::string_view function) const
19+
{
20+
#ifndef NDEBUG
21+
if (m_is_running != expected) {
22+
mio::log_error("A BasicTimer was {}running while expected to be {}. "
23+
"The offending call was {}. "
24+
"Consider using an AutoTimer with name (and scope) to avoid this.",
25+
m_is_running ? "" : "not ", expected ? "started" : "stopped", function);
26+
}
27+
// else: everything ok.
28+
29+
#else
30+
mio::unused(expected, function);
31+
#endif
32+
}
33+
34+
} // namespace timing
35+
} // namespace mio

0 commit comments

Comments
 (0)