在C中调整基于滴答的模拟中的滴答频率

时间:2013-06-11 22:03:54

标签: c time frequency

我正在用C编写一个小模拟,我希望某些事情每秒发生 N 次。

目前我有以下代码:

// Equal to 1 / N
struct timespec tick_period = (struct timespec){sec, nsec};

...

for(;;) {
    tick();
    nanosleep(&tick_period, NULL);
}

现在,这段代码没有考虑运行tick方法所花费的时间,因此每秒滴答次数会逐渐减少。

我想完成两件事:

  • 每秒运行tick N 次,没有任何偏差。
  • 如果运行tick需要花费很多时间,以致计算机无法应对每秒运行 N 次,那么可以通过某种方式调整滴答频率。

是否有一种众所周知/可接受的方式来完成这些事情?

2 个答案:

答案 0 :(得分:2)

使用clock_gettime()获取您的开始时间,然后在每个周期为其添加tick_period。然后在每个nanosleep之前计算要睡眠的增量(或使用其他一些让你睡到绝对时间的机制)。这将产生抖动(特别是如果您的Linux系统的时钟粒度不是很好)但它不会有长期错误。

要获得更好的结果,请使用CLOCK_MONOTONIC代替CLOCK_REALTIME。这将需要使用clock_nanosleep,但其优点是您可以使用TIMER_ABSTIME标志并在您的累积绝对时间之前休眠。

OSX更新:您可以使用gettimeofday()代替clock_gettime(),而您自己从timeval转换为timespec会增加烦恼。然后,您可以像以前一样使用nanosleep计算增量和睡眠。 OSX可能仍然在HZ或10ns的古老睡眠粒度下工作。这将导致很多抖动,但整体平均值将是正确的。

答案 1 :(得分:2)

我不知道是否有标准方法,但这是我用过的方法。

简而言之,确定帧速率的间隔时间并根据此时间间隔推进虚拟时钟。每一帧,确定完成“工作”所花费的时间。从帧间隔中减去工作时间可以告诉您需要多长时间才能达到下一个间隔。

这本身就会提供“每秒嘀嗒N次而没有歪斜”。它是自我纠正的,所以如果你偶尔落后,它会在工作量变轻时加速,直到它赶上来。

如果要调整帧速率以匹配工作负载,只需检查空闲时间并相应地调整间隔。

代码是一个小程序,用于演示这一点。它运行在Linux上,我不知道OS X.我选择了1/2秒的间隔,因为你可以看它运行,看看时间是否顺畅。

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

/* frame interval is in microseconds */
#define INTERVAL  500000
/* use a variable so it is adjustable */
int interval = INTERVAL;
int ideal = 0;

struct timeval start; /* start time */
void init_time()
{
    gettimeofday(&start, 0);
    wait((1000000 - start.tv_usec));
    gettimeofday(&start, 0);
    ideal = start.tv_usec; /* initialize ideal time */
}

int get_time()
{
    struct timeval tv;
    gettimeofday(&tv, 0);
    tv.tv_sec -= start.tv_sec; /* normalize to start time */
    int usec = (tv.tv_sec * 1000000) + (tv.tv_usec);
    return usec;
}

int wait(int usec)
{
    struct timespec ts = { 0, usec * 1000 };
    if (nanosleep(&ts, 0) != 0) {
        printf("ERROR: nanosleep interrupted\n");
    }
}

void dowork()
{
    wait((rand() % 5) * 100000); /* simulated workload */
}

void frame()
{
    dowork(); /* do your per-frame work here */

    int actual = get_time();
    int work_time = actual - ideal; /* elapsed time in dowork() */
    int idle_time = interval - work_time; /* idle delay to next frame */

#ifdef ENABLE_VARIABLE
    if (idle_time < 0) {
        /* OPTIONAL: slow frame rate 10% if falling behind */
        interval -= idle_time;
    } else if (interval > INTERVAL) {
        /* OPTIONAL: if we slowed down, but now we have idle time, increase
         * rate 10% until we get to our original target rate */
        interval -= (interval - INTERVAL)/10;
    }
#endif

    if (idle_time > 0) {
        /* sleep for the idle period */
        wait(idle_time);
    }

    printf("FRAME: time %10d (work %10d, idle %10d)\n",
        ideal, work_time, idle_time);

    ideal = ideal + interval;
}

void main()
{
    int i;
    init_time();
    /* simulate 50 frames */
    for (i=0; i<50; i++)
        frame();
}