diff --git a/poc/CMakeLists.txt b/poc/CMakeLists.txt index 3c3c3b7..a27ea27 100644 --- a/poc/CMakeLists.txt +++ b/poc/CMakeLists.txt @@ -9,6 +9,7 @@ add_subdirectory(compile_time_string_replacement) if(WIN32) add_subdirectory(int_to_string) + add_subdirectory(windows_waitable_timer) endif() add_subdirectory(rvo_optional) diff --git a/poc/windows_waitable_timer/CMakeLists.txt b/poc/windows_waitable_timer/CMakeLists.txt new file mode 100644 index 0000000..eb2430d --- /dev/null +++ b/poc/windows_waitable_timer/CMakeLists.txt @@ -0,0 +1,11 @@ +# This file is part of KDToolBox. +# +# SPDX-FileCopyrightText: 2026 Klarälvdalens Datakonsult AB, a KDAB Group company +# +# SPDX-License-Identifier: MIT +# +project(windows_waitable_timer CXX) + +set(CMAKE_CXX_STANDARD 23) + +add_executable(windows_waitable_timer windows_waitable_timer.cpp) diff --git a/poc/windows_waitable_timer/readme.md b/poc/windows_waitable_timer/readme.md new file mode 100644 index 0000000..f7e7ae4 --- /dev/null +++ b/poc/windows_waitable_timer/readme.md @@ -0,0 +1,10 @@ +# Windows Waitable Timer + +This is a small POC to demonstrate how Windows Waitable Timer objects work. + +They make use of the kernel to wait, instead of the less precise Sleep method. +This also frees up CPU in the thread waiting as the kernel can schedule other +things during the wait. + +In the example, a time period of 100 ms is used. The documentation suggests a longer +time such as seconds because of mobile CPU power consumption. diff --git a/poc/windows_waitable_timer/windows_waitable_timer.cpp b/poc/windows_waitable_timer/windows_waitable_timer.cpp new file mode 100644 index 0000000..3d3bae0 --- /dev/null +++ b/poc/windows_waitable_timer/windows_waitable_timer.cpp @@ -0,0 +1,83 @@ +/* +This file is part of KDToolBox. + + SPDX-FileCopyrightText: 2026 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Jonatan Wallmander + + SPDX-License-Identifier: MIT +*/ +#include +#include +#include + +namespace +{ +[[maybe_unused]] LARGE_INTEGER calculate_timer_due_in_milliseconds(const LONGLONG milliseconds) +{ + LARGE_INTEGER result; + result.QuadPart = -(1'0000LL * milliseconds); + return result; +} + +[[maybe_unused]] LARGE_INTEGER calculate_timer_due_in_microseconds(const LONGLONG milliseconds) +{ + LARGE_INTEGER result; + result.QuadPart = -(1'0LL * milliseconds); + return result; +} + +void print_elapsed_time(auto old_timestamp, auto new_timestamp) +{ + auto elapsed = new_timestamp - old_timestamp; + std::cout << std::chrono::duration_cast(elapsed).count() << " microseconds\n"; +} +} + +int main() +{ + HANDLE hTimer = CreateWaitableTimer(nullptr, FALSE, nullptr); + + LARGE_INTEGER liDueTime = calculate_timer_due_in_milliseconds(100); + + std::cout << "Waiting for 100 milliseconds, then repeatedly waiting for 100 milliseconds...\n"; + + if (!SetWaitableTimer(hTimer, &liDueTime, 100, nullptr, nullptr, 0)) + { + std::cout << "SetWaitableTimer failed (" << GetLastError() << ")\n"; + return 2; + } + + std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now(); + + if (WaitForSingleObject(hTimer, INFINITE) != WAIT_OBJECT_0) + std::cout << "WaitForSingleObject failed (" << GetLastError() << ")\n"; + + std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now(); + print_elapsed_time(t1, t2); + + std::chrono::steady_clock::time_point t_previous_period = t2; + + // wait for 1000 events + for (int i = 0; i < 1000; i++) + { + // this now waits and consumes no CPU while waiting + if (WaitForSingleObject(hTimer, INFINITE) != WAIT_OBJECT_0) + std::cout << "WaitForSingleObject failed (" << GetLastError() << ")\n"; + auto t_period = std::chrono::steady_clock::now(); + + // The output will look something like this: + // 99755 microseconds + // 100031 microseconds + // 100197 microseconds + // 100240 microseconds + // 99487 microseconds + // ... + // but over many iterations, the average will be around 100 ms + + print_elapsed_time(t_previous_period, t_period); + t_previous_period = t_period; + } + + CloseHandle(hTimer); + return 0; +}