Jacob
Jacob

Reputation: 19

My millisecond stopwatch seems to be slow

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

Answers (1)

paddy
paddy

Reputation: 63481

Your timer makes some incorrect assumptions:

  1. (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 after sleep_time has been reached due to scheduling or resource contention delays.

  2. (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.

  3. (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

Related Questions