Reputation: 19
I was bored so I wrote a quick millisecond stopwatch program in C++, but I noticed it was slower than it should be; that being the data seemed behind actual time. Here's a look at my simple code. I assume the console clearing method and/or the delay is slowing it down, but I am not sure of a solution to speed up the program for an accurate time reading.
#include <chrono>
#include <thread>
#include <Windows.h>
#include <iostream>
#include <fstream>
using namespace std;
using namespace std::this_thread; // sleep_for, sleep_until
using namespace std::chrono; // nanoseconds, system_clock,
seconds
void DELAY_IN_MILLISECONDS(int millisec) {
sleep_until(system_clock::now() + milliseconds (millisec));
}
void clearConsole()
{
system( "CLS");
}
struct Time {
unsigned int tick = 0;
unsigned int millisec = 0;
unsigned int sec = 0;
unsigned int min = 0;
unsigned int hr = 0;
unsigned int days = 0;
} timeAm;
int main()
{
cout << "Started stopwatch" << endl;
while (true)
{
//listens if user presses stop key
//stop key is ESC
//This should also write the time to a textFile
if (GetKeyState(VK_ESCAPE) & 0x8000)
{
ofstream File("time.txt");
File << "[" << timeAm.hr << " Hours] "
<< "[" << timeAm.hr << " Minutes]"
<< "[" << timeAm.min << " Seconds]"
<< "[" << timeAm.sec << " Milliseconds]";
File.close();
return 0;
}
DELAY_IN_MILLISECONDS(1); //1 second delay
//seconds
timeAm.millisec++;
//minutes
if (timeAm.millisec > 59)
{
timeAm.millisec = 0;
timeAm.sec++;
}
//hours
if (timeAm.sec > 59)
{
timeAm.sec = 0;
timeAm.min++;
}
//days
if (timeAm.min > 23)
{
timeAm.min = 0;
timeAm.hr++;
}
clearConsole(); //clears console
//prints to console
cout << "[" << timeAm.hr << " Hours] "
<< "[" << timeAm.hr << " Minutes]"
<< "[" << timeAm.min << " Seconds]"
<< "[" << timeAm.sec << " Milliseconds]";
}
}
Upvotes: 1
Views: 452
Reputation: 63481
Your timer makes some incorrect assumptions:
(incorrect) Every sleep will be precisely 1ms
As described in https://en.cppreference.com/w/cpp/thread/sleep_until
The function also may block for longer than
until
aftersleep_time
has been reached due to scheduling or resource contention delays.
(incorrect) All other code is executed instantly (takes exactly zero time).
Obviously that's a crazy notion. But yet your code at present relies on it. It will sleep for 1ms (which as per above we already know might not be actually 1ms), and then increment its millisecond count. Following that, it does some computation and some I/O before getting back around to the top of the loop to sleep for another "1ms".
By very definition, if the code between sleeps takes any time whatsoever then your clock will drift.
(incorrect) system_clock is monotonic
The documentation explicitly states it is not (i.e. the clock can be adjusted at any point in time). For a monotonic clock designed for this kind of thing, use std::steady_clock instead.
So, what approach should you take?
The way timing normally works is you begin with a time_point
and then at any time you calculate the time elapsed between then and now. With some basic math, you can then turn that into HH:MM:SS.mmm
.
Doing it like this will deal with those first two incorrect assumptions. Whenever your process wakes up you can calculate the true elapsed time, independent of how long the other code (or the sleep) took.
Roll that all together, and you can do something like this:
#include <chrono>
#include <iomanip>
#include <iostream>
#include <thread>
int main()
{
using std::chrono::duration_cast;
using std::chrono::milliseconds;
using Clock = std::chrono::steady_clock;
std::cout << std::setfill('0');
auto tbegin = Clock::now();
unsigned long elapsedMilli = -1U;
do {
Clock::duration elapsed = Clock::now() - tbegin;
unsigned long ms = duration_cast<milliseconds>(elapsed).count();
if (elapsedMilli == ms)
{
std::this_thread::sleep_until(tbegin + milliseconds(ms + 1));
continue;
}
elapsedMilli = ms;
// Build time
unsigned int t_millisec = ms % 1000; ms /= 1000;
unsigned int t_sec = ms % 60; ms /= 60;
unsigned int t_min = ms % 60; ms /= 60;
unsigned int t_hr = ms % 24; ms /= 24;
//unsigned int t_days = ms;
// Display time
std::cout << "["
<< std::setw(2) << t_hr << ":"
<< std::setw(2) << t_min << ":"
<< std::setw(2) << t_sec << "."
<< std::setw(3) << t_millisec << "]\n";
} while (elapsedMilli < 5000);
}
Live demo here: https://godbolt.org/z/fzGbvx1sa
Upvotes: 5