3
\$\begingroup\$

A very simple stopwatch. Intended to be used for simple benchmarking when you want to do a task several times, but exclude some setup code from the benchmarking.

Some things I have already considered:

  • Using the restrict keyword. I think it's overkill for this simple project.

  • Adding error checking in case you start a non-initialized (reset) clock. I left this responsibility to the user.

  • Error checking in general. Don't pass uninitialized pointers.

  • Returning the return value of gettimeofday in start and stop. Very few people would use this.

  • Returning sw in start, stop and reset. Chaining does not seem very useful here.

  • Removing the chaining in stop. Basically just added it to motivate the return values for timeDiff and timeAdd, even if I think it looks ugly. :)

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

struct timeval *timeDiff(struct timeval *out, 
    const struct timeval *a, 
    const struct timeval *b) 
{
    *out = (struct timeval) { .tv_sec  = a->tv_sec  - b->tv_sec,
                              .tv_usec = a->tv_usec - b->tv_usec };
    return out;
}

struct timeval *timeAdd(struct timeval *out, 
    const struct timeval *a, 
    const struct timeval *b) 
{
    *out = (struct timeval) { .tv_sec  = a->tv_sec  + b->tv_sec,
                              .tv_usec = a->tv_usec + b->tv_usec };
    return out;
}

struct stopwatch {
    struct timeval soFar;
    struct timeval start;
};

void reset(struct stopwatch *sw) {
    sw->soFar = sw->start = (struct timeval) {0, 0};
}

void start(struct stopwatch *sw) {
    gettimeofday(&sw->start, NULL);
}

void stop(struct stopwatch *sw) {
    struct timeval now;
    gettimeofday(&now, NULL);
    struct timeval diff;
    timeAdd(&sw->soFar, timeDiff(&diff, &now, &sw->start), &sw->soFar);
}

And here is some code if you want to try it out, but it's not intended to be a part of the review.

#include <stdio.h>

double sec(const struct stopwatch *sw) {
    const double M = 1000000;
    return (sw->soFar.tv_sec * M + sw->soFar.tv_usec)/M;
}

int main(void)
{
    struct stopwatch sw;
    reset(&sw);
    start(&sw);
    sleep(3.97);
    stop(&sw);
    printf("%f\n", sec(&sw));
    start(&sw);
    sleep(1);
    stop(&sw);
    printf("%f\n", sec(&sw));
    reset(&sw);
    start(&sw);
    sleep(1);
    stop(&sw);
    printf("%f\n", sec(&sw));
}
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

I think it's surprising that timeAdd() and timeDiff() return their results by both return value and out-parameter. I recommend just one (preferably return value). Though I do see that returning a value, rather than a pointer, is less convenient to use with the library functions.


There's no attempt to keep nanosecond values within the valid range of tv_usec, and overflows are ignored. I think that should be fixed. That's especially important for timers that might accumulate many start/stop cycles without reset.

If your target platform(s) don't have BSD/GNU timeradd() and timersub(), I'd encourage you to add your own. Perhaps something like

static struct timeval *timeAdd(struct timeval *out, 
                               const struct timeval *a, 
                               const struct timeval *b) 
{
#ifdef HAVE_TIMERADD
    timeradd(a, b, out);
#else
    out->tv_sec = a->tv_sec + b->tv_sec;
    out->tv_usec = a->tv_usec + b->tv_usec;
    if (out->tv_usec >= 1'000'000'000) {
        out->tv_usec -= 1'000'000'000;
        ++out->tv_sec;
    }
#endif
    return out;
}

static struct timeval *timeDiff(struct timeval *out, 
                                const struct timeval *a, 
                                const struct timeval *b) 
{
#ifdef HAVE_TIMERADD
    timersub(a, b, out);
#else
    out->tv_sec = a->tv_sec - b->tv_sec;
    if (a->tv_usec >= b->tv_usec) {
        out->tv_usec = a->tv_usec - b->tv_usec;
    } else {
        out->tv_usec = 1'000'000'000 + a->tv_usec - b->tv_usec;
        --out->tv_sec;
    }
#endif
    return out;
}

Looking at how the function is used, it probably makes sense for stop() to return the total so far, so we could write

printf("%f\n", timeval_to_sec(stop(&sw)));
\$\endgroup\$
5
  • \$\begingroup\$ Is it not more accurate to use clock? \$\endgroup\$
    – jdt
    Commented Dec 13, 2021 at 20:07
  • \$\begingroup\$ "return their results by both return value and out-parameter" - That is so that it is possible to chain them. Compare to most string functions in standard library. Like strcat or strcpy. It's fairly common. \$\endgroup\$
    – klutt
    Commented Dec 13, 2021 at 21:05
  • \$\begingroup\$ Your second paragraph is VERY helpful. I totally missed that. Thanks a lot. \$\endgroup\$
    – klutt
    Commented Dec 13, 2021 at 21:06
  • \$\begingroup\$ I just find it clunky that the calling code always needs to declare a variable in order to call the function, but I see your dilemma when the library functions pass by pointer rather than by value. I didn't sufficiently appreciate that issue when reviewing. Your functions have a similar signature as timeradd() and timersub() that GNU and BSD-derived systems have; the differences being that yours are const-correct, have the result object first rather than last, and as you point out, also return the result pointer. \$\endgroup\$ Commented Dec 14, 2021 at 8:13
  • \$\begingroup\$ @TobySpeight I did consider the extra variable thing, but I did not want to use hidden globals or statics. Especially since that would make it impossible to have more than one stopwatch. \$\endgroup\$
    – klutt
    Commented Dec 14, 2021 at 9:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.