【问题标题】:Strange results while measuring delta time on Linux在 Linux 上测量增量时间时出现奇怪的结果
【发布时间】:2010-05-21 12:43:29
【问题描述】:

更新:修复了代码中的增量计算,问题仍然存在

伙计们,您能否解释一下为什么我使用以下代码时常会得到非常奇怪的结果:

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

int main()
{
  struct timeval start, end;
  long mtime1, mtime2, diff;

  while(1)
  {
    gettimeofday(&start, NULL);

    usleep(2000);

    gettimeofday(&end, NULL);

    mtime1 = (start.tv_sec * 1000 + start.tv_usec/1000.0);
    mtime2 = (end.tv_sec * 1000 + end.tv_usec/1000.0);

    diff = mtime2 - mtime1;
    if(diff > 10) 
        printf("WTF: %ld\n", diff);
  }

  return 0;
}

(您可以使用以下命令编译和运行它:gcc test.c -o out -lrt && ./out

我遇到的是几乎每秒甚至更频繁地出现 diff 变量的零星大值,例如:

$ gcc test.c -o out -lrt && ./out 
WTF: 14
WTF: 11
WTF: 11
WTF: 11
WTF: 14
WTF: 13
WTF: 13
WTF: 11
WTF: 16

这怎么可能?是操作系统的错吗?它会做太多的上下文切换吗?但是我的盒子是闲置的( 平均负载:0.02、0.02、0.3)。

这是我的 Linux 内核版本:

$ uname -a
Linux kurluka 2.6.31-21-generic #59-Ubuntu SMP Wed Mar 24 07:28:56 UTC 2010 i686 GNU/Linux

【问题讨论】:

  • 不管它是什么,它不会发生在我的电脑上:/ (Linux 2.6.33-gentoo #2 SMP x86_64)
  • 你确定吗?尝试将“if(diff > 10)”行更改为“if(diff > 3)”

标签: c linux gettimeofday


【解决方案1】:

睡眠功能只能确保您至少睡眠一定时间。由于 Linux 不是实时操作系统,因此您无法确定它只会休眠您想要的时间。这是一个问题,因为您不能指望该值。正如您所指出的,睡眠时间确实很长。

Linux 调度程序不能保证这一点。有了实时操作系统,您就可以做到这一点。

你的公式在某种程度上是错误的,但我认为这不可能是你睡眠时间如此之长的原因。我用这个 sn-p 检查了两个公式,我得到了相同的结果:

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

int main()
{
  struct timeval start, end;
  long mtime, mtime2, start_time, end_time, seconds, useconds;

  while(1)
  {
    gettimeofday(&start, NULL);

    usleep(2000);

    gettimeofday(&end, NULL);

    seconds  = end.tv_sec  - start.tv_sec;
    useconds = end.tv_usec - start.tv_usec;

    mtime = ((seconds) * 1000 + useconds/1000.0) + 0.5;

    start_time = ((start.tv_sec) * 1000 + start.tv_usec/1000.0) + 0.5;
    end_time = ((end.tv_sec) * 1000 + end.tv_usec/1000.0) + 0.5;

    mtime2 = end_time - start_time;

    if(mtime > 10 || mtime2 > 10)
    {
      printf("WTF: %ld\n", mtime);
      printf("WTF2: %ld\n", mtime2);
    }
  }

  return 0;
}

结果:

$ gcc test.c -o out -lrt && ./out
WTF: 11
WTF2: 12
WTF: 21
WTF2: 21

我认为这是错误的,因为 useconds 部分是循环的,可能会导致很大的负面差异。但它不会像您使用有符号长整数那样导致如此大的时间......

my2cents

编辑:来自 man nanosleep:

目前的实现 nanosleep() 是基于正常的 内核定时器机制,它有一个 分辨率为 1/HZ s(见 time(7))。 因此, nanosleep() 总是暂停 至少在指定时间内, 但是它可能需要长达 10 毫秒的时间 比指定的直到过程 再次变为可运行。对于相同的 原因,在以下情况下返回的值 *rem 中的传递信号通常是 四舍五入到下一个较大的倍数 1/HZ 秒。

【讨论】:

  • 感谢您对 Linux 默认调度程序不是实时的解释。发现这样的问题是相当令人惊讶的。我想知道软实时系统开发人员(例如在 gamedev 中)在这种情况下会做什么,因为增量时间对此类系统有很大影响......
  • @pachanga:不客气。当我发现这一点时,我也很惊讶。这是一个问题。您可以依靠使用 nanosleep 的时间读取精度,并尝试以您可以吸收的方式实施,例如尝试在整个同步循环中仅使用一次睡眠以最大程度地减少错误。你也应该少睡一点。我可以想象更复杂的机制,但没有什么可以真正依赖但不使用睡眠 :)
  • 是的,我也在考虑“不使用睡眠”...但是在这种情况下,我的应用程序将完全消耗所有 CPU 时钟 :(
  • 如果您需要一个真正的常规计时器,请在循环模式下使用timerfd()。尽管您的唤醒可能会不时延迟,但错误不应堆积。
  • 另请注意,该手册页有点过时 - 现代 linux 使用的计时器比 1/HZ 计时器更准确,如果可用的话(并且所有现代 PC 都有这些更准确的计时器)。跨度>
【解决方案2】:

我的猜测是它与操作系统有关。尝试以实时优先级运行该进程(参见 chrt 程序),看看是否有帮助。

另一方面,您计算的 mtime 不正确。这是我使用的一个例程,虽然它是针对 struct timespec 而不是 struct timeval(纳秒而不是微秒),但原理应该很清楚:

timespec diff(timespec start, timespec end)
{
    timespec temp;
    if ((end.tv_nsec - start.tv_nsec) < 0) {
        temp.tv_sec = end.tv_sec - start.tv_sec - 1;
        temp.tv_nsec = 1000000000 + end.tv_nsec - start.tv_nsec;
    } else {
        temp.tv_sec = end.tv_sec - start.tv_sec;
        temp.tv_nsec = end.tv_nsec - start.tv_nsec;
    }
    return temp;
}

【讨论】:

  • 计算更改对我没有任何影响,但chrt 确实如此。与 OP 相同的 Linux 内核。
【解决方案3】:

找到了,看手册页

http://linux.die.net/man/3/usleep

按系统计时器的粒度。

这是 10 毫秒 afaik。因此,usleep 可能会在进程重新安排之前很久就过期。

它也与您获得的值一致,它们处于“正常”时间片的数量级。

【讨论】:

    【解决方案4】:

    我已经做过这样的措施,我的结论是完全一样的。在 Windows 和 Linux 上同样如此。

    在 10^-n 秒内构建直方图的程序给出以下结果。

    0.1 0
    0.01 0
    0.001 0
    0.0001 2
    1e-05 24
    1e-06 69726023
    Total: 69726049
    Duration: 6.47403 seconds.
    Average: 0.0928495 microseconds.
    

    但请注意,这是在全新系统上。我记得一年前在 2004 年的系统上使用过这个,每秒有几个命中率在 0.01 范围内(超过 10 毫秒)。

    【讨论】:

    • 是的。我也遇到过这种问题。问题是 sleep 保证你至少会睡一段给定的时间,但由于 linux(和 Windows)不是实时操作系统,它们可以睡得更多。看我的回答...
    【解决方案5】:

    你的公式是错误的。您必须以相同的比例转换两次。在您的示例中,女士。

    double mtime1 = (start.tv_sec * 1000 + start.tv_usec/1000.0) ;
    double mtime2 = (end.tv_sec * 1000 + end.tv_usec/1000.0) ;
    
    double diff = mtime2 - mtime1;
    if(diff > 10) 
      printf("WTF: %ld\n", diff);
    

    你必须减去修正后的值

    示例:t1 = 1.999999 t2 = 2.000001 所以间隔为 2 µs

    用你的公式计算:

    2 - 1 == 11 - 9999999 给出 (1 * 1000 - 999998 / 1000) + 0.5 的结果 == 0.502 这显然是错误的。

    我的方法给出:

    mtime1 = (1 * 1000 + 999999 / 1000) = 1999.999
    mtime2 = (2 * 1000 +      1 / 1000) = 2000.001
    
    2000.001 - 1999.999 = 0.002 ms
    

    【讨论】:

    • 是的,这是错误的,但它并没有解释如此大的差异。它导致 1 毫秒的差异,而不是 10 毫秒或更多。看我的回答。
    • 错误的公式最多错误为1s,但你是对的,这不是OP的问题。
    猜你喜欢
    • 1970-01-01
    • 2020-09-02
    • 2015-07-26
    • 1970-01-01
    • 1970-01-01
    • 2015-01-18
    • 2013-04-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多