【问题标题】:Adjusting tick frequency in tick-based simulation in C在 C 中基于刻度的仿真中调整刻度频率
【发布时间】:2013-06-07 22:10:16
【问题描述】:

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

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

【问题讨论】:

  • 什么系统/环境?
  • Mac OS X 10.8 使用标准 C 和 UNIX 库。无需担心 Windows。
  • 很遗憾,标准的 unix 方式(POSIX 计时器)在 Mac OS X 上不可用。
  • 哦。这是最不幸的。它不是 UNIX 标准的一部分吗?
  • 确实如此,但 Apple 似乎并不关心这一点。 ;-)

标签: c time frequency


【解决方案1】:

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

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

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

【讨论】:

  • 很确定这是我正在寻找的解决方案类型,谢谢。不幸的是,这些在 Mac OS X 上都不可用。
  • @Ben Jackson - UNIX/Linux 的好答案。 OP 认为不适合在稍后提及操作系统。
【解决方案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();
}

【讨论】:

    猜你喜欢
    • 2021-10-15
    • 2019-01-08
    • 1970-01-01
    • 1970-01-01
    • 2017-02-04
    • 2020-10-01
    • 1970-01-01
    • 2011-11-01
    相关资源
    最近更新 更多