Home > Net >  Calculate average time between timespec
Calculate average time between timespec

Time:07-06

I am working with an averaging function following the formula

new average = old average * (n-1) / n   (new value / n)

When passing in doubles this works great. My example code for a proof of concept is as follows.

double avg = 0;

    uint16_t i;
    for(i=1; i<10; i  ) {
        int32_t new_value = i;
        avg = avg*(i-1);
        avg /= i;
        avg  = new_value/i;
        printf("I %d New value %d Avg %f\n",i, new_value, avg);
    }

In my program I am keeping track of messages received. Each time I see a message its hit count is increased by 1, it is them timestamped using a timespec. My goal is to keep a moving average (like above) of the average time between messages of a certain type being received.

My initial attempt was to average the tv_nsec and tv_sec separately as follows

static int32_t calc_avg(const int32_t current_avg, const int32_t new_value, const uint64_t n) {
    int32_t new__average = current_avg;
    new__average = new__average*(n-1);
    new__average /= n;
    new__average  = new_value/n;
    return new__average;
}

void average_timespec(struct timespec* average, const struct timespec new_sample, const uint64_t n) {
    if(n > 0) {
        average->tv_nsec = calc_avg(average->tv_nsec, new_sample.tv_nsec, n);
        average->tv_sec = calc_avg(average->tv_sec, new_sample.tv_sec, n);
    }
}

My issue is I am using integers, the values are always rounded down and my averages are way off. Is there a smarter/easier way to average the time between timespec readings?

CodePudding user response:

Below is some code that I've used a lot [in production S/W] for years.

The main idea is that just because clock_gettime uses struct timespec does not mean this has to be "carried around" everywhere:

  1. It's easier to convert to a long long or double and propagate those values as soon as they're gotten from clock_gettime.

  2. All further math is simple add/subtract, etc.

  3. The overhead of the clock_gettime call dwarfs the multiply/divide time in the conversion.

Whether I use the fixed nanosecond value or the fractional seconds value depends upon the exact application.

In your case, I'd probably use the double since you already have calculations that work for that.

Anyway, this is what I use:

#include <time.h>

typedef long long tsc_t;                    // timestamp in nanoseconds

#define TSCSEC      1000000000LL
#define TSCSECF     1e9

tsc_t tsczero;                              // initial start time
double tsczero_f;                           // initial start time

// tscget -- get number of nanoseconds
tsc_t
tscget(void)
{
    struct timespec ts;
    tsc_t tsc;

    clock_gettime(CLOCK_MONOTONIC,&ts);
    tsc = ts.tv_sec;
    tsc *= TSCSEC;
    tsc  = ts.tv_nsec;

    tsc -= tsczero;

    return tsc;
}

// tscgetf -- get fractional number of seconds
double
tscgetf(void)
{
    struct timespec ts;
    double sec;

    clock_gettime(CLOCK_MONOTONIC,&ts);

    sec = ts.tv_nsec;
    sec /= TSCSECF;
    sec  = ts.tv_sec;

    sec -= tsczero_f;

    return sec;
}

// tscsec -- convert tsc value to [fractional] seconds
double
tscsec(tsc_t tsc)
{
    double sec;

    sec = tsc;
    sec /= TSCSECF;

    return sec;
}

// tscinit -- initialize base time
void
tscinit(void)
{

    tsczero = tscget();
    tsczero_f = tscsec(tsczero);
}

CodePudding user response:

  1. Use better integer math.
  • Use signed math if new_value < 0 possible, else int64_t cast not needed below.

  • Form the sum first and then divide.

  • Round.

Sample code:

// new__average = new__average*(n-1);
// new__average /= n;
// new__average  = new_value/n;

//              v-------------------------------------v  Add first
new__average = (new__average*((int64_t)n-1)   new_value   n/2)/n;
//                            Add n/2 to effect rounding  ^-^ 

  1. On review, the whole idea of doing averages in 2 parts is flawed. Instead use a 64-bit count of nanoseconds. Good until the year 2263.

Suggested code:

void average_timespec(int64_t* average, struct timespec new_sample, int64_t n) {
  if (n > 0) {
    int64_t t = new_sample.tv_sec   new_sample.tv_nsec*(int64_t)1000000000;
    *average = (*average*(n-1)   t   n/2)/n; 
  }
}  

If you must form a struct timespec from the average, easy to do when average >= 0.

int64_t average;
average_timespec(&average, new_sample, n);
struct timespec avg_ts = (struct timespec){.tm_sec = average/1000000000, 
    .tm_nsec = average00000000);
  • Related