计算 timespec 之间的平均时间

问题描述 投票:0回答:4

我正在使用以下公式的平均函数

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

当传递双打时,这非常有效。我的概念证明示例代码如下。

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);
    }

在我的程序中,我正在跟踪收到的消息。每次我看到一条消息时,它的点击数都会增加 1,这是使用

timespec
标记的。我的目标是保持接收某种类型的消息之间的平均时间的移动平均值(如上)。

我最初的尝试是分别对

tv_nsec
tv_sec
进行平均,如下

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);
    }
}

我的问题是我使用的是整数,这些值总是向下舍入,而我的平均值相差很大。有没有更智能/更简单的方法来平均

timespec
读数之间的时间?

c moving-average timespec
4个回答
1
投票

下面是一些我已经使用了很多[在生产软件中]多年的代码。

主要思想是,仅仅因为

clock_gettime
使用
struct timespec
not 就意味着它必须“随身携带”到任何地方:

  1. long long
    获取
    double
    clock_gettime 并传播
    那些
    值会更容易。

  2. 所有进一步数学都是简单的加/减等。

  3. clock_gettime
    调用的开销使转换中的乘法/除法时间相形见绌。

我是否使用固定纳秒值或小数秒值取决于具体的应用程序。

就您而言,我可能会使用

double
,因为您已经有了适用于此的计算。

不管怎样,这就是我用的:

#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);
}

0
投票
  1. 使用更好的整数数学。
  • 如果可能,请使用有符号数学,否则下面不需要

    new_value < 0
    转换。
    
    

  • 先求和,再除。
  • 圆形。
  • 示例代码:

int64_t



经审查,分两部分进行平均的整个想法是有缺陷的。而是使用 64 位纳秒计数。直到 2263 年为止都很好。
  1. 建议代码:

// 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 ^-^

如果必须从平均值中形成
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; } }

,当

struct timespec
时很容易做到。
average >= 0



0
投票

在大多数情况下,这是通过保留小数点后 N 位来完成的。不幸的是,使用

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

结构,小数点将是从 0 到 999,999,999 的数字,这不是 2 的幂。我仍然认为你可以实现它。

使用支持 

timespec

类型的编译器,您可以先将

__int128
转换为
timespec
,然后根据该数字进行数学运算。
__int128

现在您可以使用 
__int128 t = ts.tv_nsec + ts.tv_sec * 1000000000;

计算平均值,其精度为小数点后 9 位。如果你想要更精确一点,比如另外 2 位数字,你可以使用:

t

即左右各乘以 100。

_注意:非常大的数字可能需要转换;因此,可能需要编写上面的内容,以确保数学按预期工作:

__int128 t = ts.tv_nsec * 100 + ts.tv_sec * 100000000000;

没有办法写出完整的
const __int128 one_hundred = 100; const __int128 one_hundred_billion = 100000000000; const __int128 seconds = ts.tv_sec; const __int128 nanoseconds = ts.tv_nsec; __int128 t = nanoseconds * one_hundred + seconds * one_hundred_billion;

文字数字(据我所知),您可能需要使用一些数学来使用它。

现在 

__int128

的精度为 11 位。用这些定点值进行数学运算 - 即加法和减法就可以了,乘法和除法需要额外的工作......

t

您仍然会遇到类似的精度误差,只是比使用普通整数小得多。只是普通整数和定点数之间的数学运算更难理解。如果使用双打就足够了,那么克雷格的解决方案是最简单的。


0
投票

__int128 t3 = t1 + t2; // t3 is defined as expected __int128 t3 = t1 - t2; __int128 t6 = t4 * t5; // t6 is not as expected, it was multiplied by another billion __int128 t7 = t4 * t5 / one_billion; // or `one_hundred_billion` // t7 is valid, same number of decimal digits __int128 t10 = t9 / t8; // t10 is missing decimal digits __int128 t11 = t9 * one_billion / t8; // t11 is valid __int128 t12 = t9 / n; // this is valid if n is a regular integer (no decimal digits) etc.

在计算 
#include <time.h> timespec_t timespec_average(timespec_t ts_pre, timespec_t ts_post) { timespec_t average; average.tv_nsec = ts_pre.tv_nsec; // one second in nanoseconds long sec_in_ns = 1000000000L; // sec average.tv_sec = ts_pre.tv_sec + ((ts_post.tv_sec - ts_pre.tv_sec) / 2); // nsec if (ts_pre.tv_sec == ts_post.tv_sec) { average.tv_nsec += (ts_post.tv_nsec - ts_pre.tv_nsec) / 2; } else { if (ts_pre.tv_nsec <= ts_post.tv_nsec) { average.tv_nsec += (ts_post.tv_nsec - ts_pre.tv_nsec) / 2; if ((ts_post.tv_sec - ts_pre.tv_sec) % 2 != 0) { average.tv_nsec += sec_in_ns / 2; } } else { average.tv_nsec += (sec_in_ns - ts_pre.tv_nsec + ts_post.tv_nsec) / 2; } while (sec_in_ns <= average.tv_nsec) { average.tv_nsec -= sec_in_ns; ++average.tv_sec; } } return average; }

平均值时,您必须小心不要溢出

int
边界,因为
tv_sec
没有严格定义,并且没有像
time_t
那样的上限值。基本上不要尝试将这两个值相加就可以了。
    

© www.soinside.com 2019 - 2024. All rights reserved.