scvalex.net

20. Fixed Frequency Loops

Suppose we want to stress-test a server by hitting it with a fixed number of requests per second. Or maybe we want to write a game loop that runs at a fixed number of frames per second. In both cases, we want to run some code at a fixed frequency \(\nu\). More precisely, we want a loop that calls some function, sleeps for a bit, then restarts, and overall, the function is called \(\nu\) times per second.

First, we need a way to time code. The function gettimeofday(2) gives us a timeval, which represents the current time in seconds and microseconds. By bracketing our code in a couple of these, we can find exactly how many microseconds it took to run.

Given that we know the frequency \(\nu\), we also know that our code should take \(1/\nu\) seconds to run. We time our code, and if it runs too quickly, we insert an artificial pause. In other words, if our code runs in \(t\) seconds, and \(t < 1/\nu\), we should pause for \(1/\nu - t\) seconds.

We can use usleep(3) to pause for a given number of microseconds. In our case, we want to usleep for \((1/\nu - t) * 1\_000\_000\) microseconds. Note that usleep is unavailable on some older Unix systems; pselect(2) can be used instead.

Putting it all together, we get the following code:

#include <stdio.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>

/* How many times per second do we want the code to run? */
const int frequency = 2;

/* Pretend to do something useful. */
void do_work() {
    volatile int i;
    for (i = 0; i < 10000000; ++i)
        ;
}

int main(int argc, char *argv[]) {
    /* How long should each work unit take? */
    long slice = (long)(1.0 / frequency * 1000000);

    struct timeval beginning;
    gettimeofday(&beginning, NULL);
    struct timeval last_tick = beginning;

    long total = 0;
    int tick;
    for (tick = 1; 1; ++tick) {
        do_work();

        struct timeval now;
        gettimeofday(&now, NULL);

        /* How much time has passed since the last tick? */
        long usec_elapsed = (now.tv_sec - last_tick.tv_sec) * 1000000
                          + (now.tv_usec - last_tick.tv_usec);
        last_tick = now;

        /* How time did we spend working this tick? */
        long usec_work = (now.tv_sec - beginning.tv_sec) * 1000000
                          + (now.tv_usec - beginning.tv_usec);

        total += usec_elapsed;
        printf("Worked for %ldus. Average time per tick: %ldus (%ldus since last).\n",
               usec_work, total / tick, usec_elapsed);

        /* Pause if appropriate. */
        long usec_tosleep = slice - usec_work;
        if (usec_tosleep > 0) {
            printf("Sleeping for %ldus.\n", usec_tosleep);
            usleep(usec_tosleep);
        }

        /* Prepare for the next tick. */
        gettimeofday(&beginning, NULL);
    }

    return 0;
}

The code itself is unsurprising, but it’s easy to get wrong: if, when calculating the length of the pause, you use the time for the entire loop, usec_elapsed, instead of the time for the work part of it, usec_work, you end up waiting alternatively for \(1/\nu\) and \(0\).