diff --git a/src/shambase/include/shambase/Timer.hpp b/src/shambase/include/shambase/Timer.hpp new file mode 100644 index 0000000000..e619a347e6 --- /dev/null +++ b/src/shambase/include/shambase/Timer.hpp @@ -0,0 +1,87 @@ +// -------------------------------------------------------// +// +// SHAMROCK code for hydrodynamics +// Copyright (c) 2021-2026 Timothée David--Cléris +// SPDX-License-Identifier: CeCILL Free Software License Agreement v2.1 +// Shamrock is licensed under the CeCILL 2.1 License, see LICENSE for more information +// +// -------------------------------------------------------// + +#pragma once + +/** + * @file Timer.hpp + * @author Timothée David--Cléris (tim.shamrock@proton.me) + * @brief High-resolution timer with plf_nanotimer (non-macOS) or std::chrono (macOS) backend + * + */ + +#include "shambase/aliases_float.hpp" +#include "shambase/format_time.hpp" + +#ifndef __MACH__ + #define USE_PLF_TIMER +#endif + +#if defined(USE_PLF_TIMER) + #include +#else + #include +#endif + +namespace shambase { + /** + * @brief Class Timer measures the time elapsed since the timer was started. + */ + class Timer { + public: +#if defined(USE_PLF_TIMER) + plf::nanotimer timer; ///< Internal timer +#else + std::chrono::steady_clock::time_point t_start; ///< Internal timer +#endif + f64 nanosec; ///< Time in nanoseconds + + /// Constructor, init nanosec to 0 + Timer() : nanosec(0.0) {} + + /** + * @brief Starts the timer. + */ + inline void start() { +#if defined(USE_PLF_TIMER) + timer.start(); +#else + t_start = std::chrono::steady_clock::now(); +#endif + } + + /** + * @brief Stops the timer and stores the elapsed time in nanoseconds. + * + * If the timer has already been stopped, calling this again updates `nanosec` to + * the new delta since `start()`. + */ + inline void stop() { +#if defined(USE_PLF_TIMER) + nanosec = f64(timer.get_elapsed_ns()); +#else + auto t_end = std::chrono::steady_clock::now(); + nanosec = f64( + std::chrono::duration_cast(t_end - t_start).count()); +#endif + } + + /** + * @brief Converts the stored nanosecond time to a string representation. + * @return std::string A string representation of the elapsed time. + */ + inline std::string get_time_str() const { return nanosec_to_time_str(nanosec); } + + /** + * @brief Converts the stored nanosecond time to a floating point representation in seconds. + * @return f64 The elapsed time in seconds. + */ + [[nodiscard]] inline f64 elapsed_sec() const { return nanosec * 1e-9; } + }; +} // namespace shambase diff --git a/src/shambase/include/shambase/format_time.hpp b/src/shambase/include/shambase/format_time.hpp new file mode 100644 index 0000000000..c1876e829e --- /dev/null +++ b/src/shambase/include/shambase/format_time.hpp @@ -0,0 +1,66 @@ +// -------------------------------------------------------// +// +// SHAMROCK code for hydrodynamics +// Copyright (c) 2021-2026 Timothée David--Cléris +// SPDX-License-Identifier: CeCILL Free Software License Agreement v2.1 +// Shamrock is licensed under the CeCILL 2.1 License, see LICENSE for more information +// +// -------------------------------------------------------// + +#pragma once + +/** + * @file format_time.hpp + * @author Timothée David--Cléris (tim.shamrock@proton.me) + * @brief Human-readable nanosecond duration formatting + * + */ + +#include "shambase/string.hpp" + +namespace shambase { + + /** + * @brief Convert nanoseconds to a human-readable string representation. + * + * @param nanosec The duration in nanoseconds. + * @return std::string The duration in a human-readable format. + */ + inline std::string nanosec_to_time_str(double nanosec) { + double sec_int = nanosec; + + std::string unit = "ns"; + + if (sec_int > 2000) { + unit = "us"; + sec_int /= 1000; + } + + if (sec_int > 2000) { + unit = "ms"; + sec_int /= 1000; + } + + if (sec_int > 2000) { + unit = "s"; + sec_int /= 1000; + } + + if (sec_int > 2000) { + unit = "ks"; + sec_int /= 1000; + } + + if (sec_int > 2000) { + unit = "Ms"; + sec_int /= 1000; + } + + if (sec_int > 2000) { + unit = "Gs"; + sec_int /= 1000; + } + + return shambase::format("{:.2f} {}", sec_int, unit); + } +} // namespace shambase diff --git a/src/shambase/include/shambase/time.hpp b/src/shambase/include/shambase/time.hpp index 4eefc0742e..aeafd107c4 100644 --- a/src/shambase/include/shambase/time.hpp +++ b/src/shambase/include/shambase/time.hpp @@ -16,118 +16,12 @@ * */ +#include "shambase/Timer.hpp" #include "shambase/aliases_float.hpp" -#include "shambase/string.hpp" #include -#include - -#ifndef __MACH__ - #include -#else - #include -#endif namespace shambase { - /** - * @brief Convert nanoseconds to a human-readable string representation. - * - * @param nanosec The duration in nanoseconds. - * @return std::string The duration in a human-readable format. - */ - inline std::string nanosec_to_time_str(double nanosec) { - double sec_int = nanosec; - - std::string unit = "ns"; - - if (sec_int > 2000) { - unit = "us"; - sec_int /= 1000; - } - - if (sec_int > 2000) { - unit = "ms"; - sec_int /= 1000; - } - - if (sec_int > 2000) { - unit = "s"; - sec_int /= 1000; - } - - if (sec_int > 2000) { - unit = "ks"; - sec_int /= 1000; - } - - if (sec_int > 2000) { - unit = "Ms"; - sec_int /= 1000; - } - - if (sec_int > 2000) { - unit = "Gs"; - sec_int /= 1000; - } - - return shambase::format_printf("%4.2f", sec_int) + " " + unit; - } - - /** - * @brief Class Timer measures the time elapsed since the timer was started. - */ - class Timer { - public: -#if defined(__MACH__) - std::chrono::steady_clock::time_point t_start; ///< Internal timer -#else - plf::nanotimer timer; ///< Internal timer -#endif - f64 nanosec; ///< Time in nanoseconds - - /// Constructor, init nanosec to 0 - Timer() : nanosec(0.0) {} - - /** - * @brief Starts the timer. - */ - inline void start() { -#if defined(__MACH__) - t_start = std::chrono::steady_clock::now(); -#else - timer.start(); -#endif - } - - /** - * @brief Stops the timer and stores the elapsed time in nanoseconds. - * - * If the timer has already been stopped, calling this again updates `nanosec` to - * the new delta since `start()`. - */ - inline void stop() { -#if defined(__MACH__) - auto t_end = std::chrono::steady_clock::now(); - nanosec = f64( - std::chrono::duration_cast(t_end - t_start).count()); -#else - nanosec = f64(timer.get_elapsed_ns()); -#endif - } - - /** - * @brief Converts the stored nanosecond time to a string representation. - * @return std::string A string representation of the elapsed time. - */ - inline std::string get_time_str() const { return nanosec_to_time_str(nanosec); } - - /** - * @brief Converts the stored nanosecond time to a floating point representation in seconds. - * @return f64 The elapsed time in seconds. - */ - [[nodiscard]] inline f64 elapsed_sec() const { return nanosec * 1e-9; } - }; - /** * @brief Class FunctionTimer measures the time it takes to execute a function. * diff --git a/src/tests/shambase/timeTests.cpp b/src/tests/shambase/timeTests.cpp new file mode 100644 index 0000000000..e54d404af7 --- /dev/null +++ b/src/tests/shambase/timeTests.cpp @@ -0,0 +1,100 @@ +// -------------------------------------------------------// +// +// SHAMROCK code for hydrodynamics +// Copyright (c) 2021-2026 Timothée David--Cléris +// SPDX-License-Identifier: CeCILL Free Software License Agreement v2.1 +// Shamrock is licensed under the CeCILL 2.1 License, see LICENSE for more information +// +// -------------------------------------------------------// + +#include "shambase/time.hpp" +#include "shamtest/shamtest.hpp" +#include + +TestStart(Unittest, "shambase/time/start_stop_elapsed_gt_zero", unitt_timer_start_stop_elapsed, 1) { + + shambase::Timer timer; + + timer.start(); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + timer.stop(); + + REQUIRE(timer.elapsed_sec() > 0); + + std::string time_str = timer.get_time_str(); + REQUIRE(!time_str.empty()); +} + +TestStart(Unittest, "shambase/time/sleep_200ms_precision", unitt_timer_sleep_200ms, 1) { + + shambase::Timer timer; + timer.start(); + std::this_thread::sleep_for(std::chrono::milliseconds(400)); + timer.stop(); + + // sadly i must be verrrrrrry loose on the tolerances because of Github runners ... + REQUIRE_FLOAT_EQUAL(timer.elapsed_sec(), 0.4, 0.2); +} + +TestStart(Unittest, "shambase/time/stop_overwrites_nanosec", unitt_timer_stop_overwrites, 1) { + + shambase::Timer timer; + + timer.start(); + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + timer.stop(); + f64 elapsed1 = timer.elapsed_sec(); + + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + timer.stop(); + f64 elapsed2 = timer.elapsed_sec(); + + REQUIRE(elapsed1 < elapsed2); +} + +TestStart(Unittest, "shambase/time/reusability", unitt_timer_reusability, 1) { + + shambase::Timer timer; + + timer.start(); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + timer.stop(); + f64 elapsed1 = timer.elapsed_sec(); + + timer.start(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + timer.stop(); + f64 elapsed2 = timer.elapsed_sec(); + + REQUIRE(elapsed1 > elapsed2); +} + +TestStart(Unittest, "shambase/time/get_time_str_has_unit", unitt_timer_get_time_str_format, 1) { + + shambase::Timer timer; + timer.start(); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + timer.stop(); + + std::string s = timer.get_time_str(); + + REQUIRE(!s.empty()); + REQUIRE(s.find("ms") != std::string::npos || s.find("us") != std::string::npos); +} + +TestStart( + Unittest, "shambase/time/nanosec_to_time_str_all_units", unitt_nanosec_to_time_str_various, 1) { + + using namespace shambase; + + REQUIRE(nanosec_to_time_str(0) == "0.00 ns"); + REQUIRE(nanosec_to_time_str(500) == "500.00 ns"); + REQUIRE(nanosec_to_time_str(2500) == "2.50 us"); + REQUIRE(nanosec_to_time_str(5000000) == "5.00 ms"); + REQUIRE(nanosec_to_time_str(2500000) == "2.50 ms"); + REQUIRE(nanosec_to_time_str(5000000000) == "5.00 s"); + REQUIRE(nanosec_to_time_str(2500000000) == "2.50 s"); + REQUIRE(nanosec_to_time_str(2500000000000) == "2.50 ks"); + REQUIRE(nanosec_to_time_str(2500000000000000) == "2.50 Ms"); + REQUIRE(nanosec_to_time_str(2500000000000000000) == "2.50 Gs"); +}