I implemented a timer using boost::asio::deadline_timer.
I run a timer using expires_from_now(boost::posix_time::milliseconds(1))
and I count, how often it triggers during the period of 10 seconds (under Windows). I expect 10 000 times.
Results are following:
On one PC counter is very accurate - 10 000 times per 10 seconds.
On other PC counter varies between 7000 and 8500 randomly.
Problem: after some time, the count reduces to 600-800 times per 10 seconds.
I cannot find out what is the reason of increasing of the timeout to ~10-15ms when I still use a timer with 1ms interval.
I checked processor power settings in power management - minimum performance is 100% . Is there any other settings in Windows that could influence the result on different PCs?
Why this happens after some time of running a program?
Instead of waiting "x" milliseconds, if you depend on the deadlines being met with the best possible precision, just state them absolutely:
Live On Coliru
#include <boost/asio.hpp>
#include <boost/asio/high_resolution_timer.hpp>
#include <iostream>
namespace ba = boost::asio;
using namespace std::chrono_literals;
int main() {
ba::io_context io;
using C = ba::high_resolution_timer::clock_type;
ba::high_resolution_timer t(io);
auto next_wakeup = [&t, interval = 10ms] {
t.expires_at(C::now() + interval);
t.wait();
};
auto until = C::now() + 5s;
int count = 0;
do count += 1;
while (next_wakeup(), C::now() <= until);
std::cout << "Timer triggered " << count << " times in 5s\n";
}
On my system it reports 497, so you can see the loop overhead is enough to miss a few deadlines in total. This becomes more significant if you make the frequency lower.
Alternative Approach
You could of course make the thing multi threaded and distribute your timer events across threads so there will be fewer missed ones. Or you could look at things like the experimental scheduler in Boost Thread
If you change the design trade-offs to minimize missed events, at the cost of (likely) having noisier frequency/intervals:
Note how care is taken to calculate the next event back from the starting point each time, so that INTERVAL
may be specified with accuracy that isn't representable in the clock's time_point
:
auto constexpr INTERVAL = 1.0/3ms;
representation. Doing otherwise risks accumulating rounding errors.
Live On Coliru
#include <boost/asio.hpp>
#include <boost/asio/high_resolution_timer.hpp>
#include <boost/thread.hpp>
using namespace std::chrono_literals;
namespace /*static*/ {
auto constexpr INTERVAL = 1ms;
auto constexpr DURATION = 5s;
std::atomic_int count {0};
void on_timer_event() { ++count; }
}
namespace ba = boost::asio;
using Timer = ba::high_resolution_timer;
using C = Timer::clock_type;
template <typename Interval>
static void timer_chain(Timer& t, C::time_point start_point, Interval ival, int n = 0) {
t.expires_at(start_point + std::chrono::duration_cast<C::duration>(n * ival));
t.async_wait([=,&t](auto ec) {
if (!ec) {
on_timer_event();
timer_chain(t, start_point, ival, n+1);
}
});
}
#include <iostream>
int main() {
ba::io_context io;
boost::thread_group tg;
std::list<Timer> timers;
auto const slices = 10;
auto const start_point = C::now();
auto group_interval = INTERVAL * slices;
for (auto slice = 0; slice<slices; ++slice)
timer_chain(timers.emplace_back(io), start_point + slice*INTERVAL, group_interval);
for (unsigned i = 0; i < std::thread::hardware_concurrency(); ++i)
tg.create_thread([&io] { io.run_for(DURATION); });
std::cout << "Running on " << tg.size() << " threads...\n";
tg.join_all();
std::cout << "Event triggered " << count << " times in " << (C::now() - start_point)/1ms << "ms\n";
}
This prints
Running on 1 threads...
Event triggered 5002 times in 5001ms
Or, on my system:
Running on 8 threads...
Event triggered 5002 times in 5001ms