|
| 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 | +} |
0 commit comments