2

我正在用 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次,那么可以通过某种方式调整滴答的频率。

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

4

2 回答 2

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();
}
于 2013-06-12T02:00:08.523 回答
2

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

为了获得更好的结果,请使用CLOCK_MONOTONIC而不是CLOCK_REALTIME. 这将需要使用clock_nanosleep,但优点是您可以使用该TIMER_ABSTIME标志并休眠直到您的累积绝对时间。

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

于 2013-06-11T22:29:48.190 回答