Search code examples
c++linuxtimegettimeofday

Control loop time with usleep


I try to make sure the execution time of each loop to 10ms with usleep , but sometimes it exceeds 10ms.

I have no idea how to solve this problem, is it proper to use usleep and gettimeofday in this case?

Please help my find out what i missed.

Result: 0.0127289 0.0136499 0.0151598 0.0114031 0.014801

double tvsecf(){
     struct timeval tv;
     double asec;

     gettimeofday(&tv,NULL);
     asec = tv.tv_usec;
     asec /= 1e6;
     asec += tv.tv_sec;

     return asec;
}
int main(){
    double t1 ,t2;
    t1 = tvsecf();
    for(;;){
        t2= tvsecf();
        if(t2-t1 >= 0.01){
            if(t2-t1 >= 0.011)
                cout << t2-t1 <<endl;
            t1 = tvsecf();
        }
        usleep(100);
    }
}

Solution

  • To keep the loop overhead (which is generally unknown) from constantly accumulating error, you can sleep until a time point, instead of for a time duration. Using C++'s <chrono> and <thread> libraries, this is incredibly easy:

    #include <chrono>
    #include <iostream>
    #include <thread>
    
    int
    main()
    {
        using namespace std;
        using namespace std::chrono;
        auto t0 = steady_clock::now() + 10ms;
        for (;;)
        {
            this_thread::sleep_until(t0);
            t0 += 10ms;
        }
    }
    

    One can dress this up with more calls to steady_clock::now() in order to ascertain the time between iterations, and perhaps more importantly, the average iteration time:

    #include <chrono>
    #include <iostream>
    #include <thread>
    
    int
    main()
    {
        using namespace std;
        using namespace std::chrono;
        using dsec = duration<double>;
        auto t0 = steady_clock::now() + 10ms;
        auto t1 = steady_clock::now();
        auto t2 = t1;
        constexpr auto N = 1000;
        dsec avg{0};
        for (auto i = 0; i < N; ++i)
        {
            this_thread::sleep_until(t0);
            t0 += 10ms;
            t2 = steady_clock::now();
            dsec delta = t2-t1;
            std::cout << delta.count() << "s\n";
            avg += delta;
            t1 = t2;
        }
        avg /= N;
        cout << "avg = " << avg.count() << "s\n";
    }
    

    Above I've added to the loop overhead by doing more things within the loop. However the loop is still going to wake up about every 10ms. Sometimes the OS will wake the thread late, but next time the loop automatically adjusts itself to sleep for a shorter time. Thus the average iteration rate self-corrects to 10ms.

    On my machine this just output:

    ...
    0.0102046s
    0.0128338s
    0.00700504s
    0.0116826s
    0.00785826s
    0.0107023s
    0.00912614s
    0.0104725s
    0.010489s
    0.0112545s
    0.00906409s
    avg = 0.0100014s