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:
It's easier to convert to a
long long
ordouble
and propagate those values as soon as they're gotten fromclock_gettime
.All further math is simple add/subtract, etc.
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:
- Use better integer math.
Use signed math if
new_value < 0
possible, elseint64_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 ^-^
- 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);