【问题标题】:gettimeofday() microsecond not limited to below secondgettimeofday() 微秒不限于秒以下
【发布时间】:2018-05-26 22:59:48
【问题描述】:

当我输出gettimeofday() 的微秒字段时,我注意到微秒字段大于 1,000,000。有人知道为什么是这样吗?这是否意味着我一直在解释 gettimeofday() 错误?

作为记录,我的假设是根据gettimeofday() 的当前时间(以微秒为单位)如下:

struct timeval ts;
gettimeofday(&ts, NULL); 
printf("%zu", ts.tv_sec * 1000000 + ts.tv_usec);

编辑:这是导致问题的代码。在下面的 cmets 之后, printf() 可能有问题。

struct timeval curr_time;
gettimeofday(&curr_time, NULL);
printf("Done-arino! Onto the matrix multiplication (at %zu s, %03zu ms)\n", curr_time.tv_sec, curr_time.tv_usec);

// Matrix Multiplication
struct timeval start_tv, end_tv, elapsed_tv;
gettimeofday(&start_tv, NULL);
for (i = 0; i < N; i++)
    for (j = 0; j < N; j++)
        for (k = 0; k < N; k++)
            C[i][j] += A[i][k] * B[k][j];
gettimeofday(&end_tv, NULL);
timersub(&end_tv, &start_tv, &elapsed_tv);

// Print results
printf("Elapsed time: %zu s, %03zu ms\n", elapsed_tv.tv_sec, elapsed_tv.tv_usec / 1000);

【问题讨论】:

  • 您需要更仔细地识别您的平台,并向我们展示一个演示问题的 MCVE (minimal reproducible example)。您显示的内容并未证明 tv_usec 等于或大于一百万,这就是您所声称的。如下所述,tv_usec 应该小于一百万是正确的。
  • 您对tv_usec 应该小于1000000 的怀疑是正确的。如果看到tv_usec 大于或等于 1000000,我会感到非常惊讶(有一个例外,这里不适用)。你能告诉我们更多关于你看到大于 1000000 的价值吗?
  • 更有说服力的代码可能是:int main(void) { while (1) { struct timeval ts; if (gettimeofday(&amp;ts, 0) == 0 &amp;&amp; ts.tv_usec &gt;= 1000000) printf("%lu s; %lu µs\n", (long)ts.tv_sec, (long)ts.tv_usec); } return 0; }.
  • 一秒,马上发布我的代码。
  • 你需要显示timersub()——虽然它可能是系统提供的,在这种情况下你需要识别系统。但是您的代码中没有任何内容表明 gettimeofday() 是错误的。您尚未打印大于 999,999 的 tv_usec 值。

标签: c time gettimeofday


【解决方案1】:

gettimeofday成功后,是的,tv_usec保证严格小于1000000。

如果您(认为自己)看到了 1000000 或更大的值,那么是的,很可能您做错了什么。

一个常见的错误是天真地添加或减去两个struct timeval 值,而没有在tv_sectv_usec 字段之间实现正确的进位或借位,这很容易导致@987654327 中的值(错误和错误) @ 大于 1000000。(在您编辑的帖子中,您提到减去时间规格,但您使用的是系统提供的 timersub 函数,它应该可以正确借用。)

如果您使用的是 struct timespec 而不是 struct timeval,并且 if 闰秒正在进行,并且 if 您(奇迹般地)使用操作系统实现了 Markus Kuhn 在https://www.cl.cam.ac.uk/~mgk25/posix-clocks.html 提出的CLOCK_UTC 时钟类型的内核,你会看到tv_nsec 的值大于1000000000,但这是很多“如果”。 (据我所知,没有一个广泛使用的内核实现过CLOCK_UTC。)

【讨论】:

    【解决方案2】:

    您的printf 格式存在问题,可能会导致此问题。

    %zu 格式用于打印size_t 值。但是tv_sectv_usec 都没有size_t 类型。

    在现代系统上,size_t 可能是 64 位。但如果 tv_sectv_usec 不是,printf 将错误地打印这些值。

    我把你的printfs 改成了

    printf("Done-arino! Onto the matrix multiplication (at %ld s, %03u ms)\n",
            curr_time.tv_sec, curr_time.tv_usec);
    

    printf("Elapsed time: %ld s, %03u ms\n",
            elapsed_tv.tv_sec, elapsed_tv.tv_usec / 1000);
    

    不过,这些不一定适合您 - 这取决于您的系统对 tv_sectv_usec 的具体选择。

    【讨论】:

      【解决方案3】:

      您需要展示一些更有说服力的代码,并确定您遇到问题的平台。

      例如:

      #include <stdio.h>
      #include <sys/time.h>
      
      int main(void)
      {
          while (1)
          {
              struct timeval ts;
              if (gettimeofday(&ts, 0) == 0 && ts.tv_usec >= 1000000)
                  printf("%lu s; %lu µs\n", (long)ts.tv_sec, (long)ts.tv_usec); 
          }
          return 0;
      }
      

      非常繁忙的循环有点令人讨厌;也许您应该在每次迭代时使用nanosleep() 休眠一两微秒:

      #include <stdio.h>
      #include <sys/time.h>
      #include <time.h>
      
      int main(void)
      {
          while (1)
          {
              struct timeval tv;
              if (gettimeofday(&tv, 0) == 0 && tv.tv_usec >= 1000000)
                  printf("%lu s; %lu µs\n", (long)tv.tv_sec, (long)tv.tv_usec); 
              struct timespec ts = { .tv_sec = 0, .tv_nsec = 2000 };
              nanosleep(&ts, 0);
          }
          return 0;
      }
      

      或者,包括一个进度表来证明代码正在运行:

          #include <stdio.h>
          #include <sys/time.h>
          #include <time.h>
      
          int main(void)
          {
              size_t loop_count = 0;
              size_t line_count = 0;
      
              while (1)
              {
                  struct timeval tv;
                  if (gettimeofday(&tv, 0) == 0 && tv.tv_usec >= 1000000)
                      printf("%lu s; %lu µs\n", (long)tv.tv_sec, (long)tv.tv_usec); 
                  struct timespec ts = { .tv_sec = 0, .tv_nsec = 2000 };
                  nanosleep(&ts, 0);
                  if (++loop_count > 100000)
                  {
                      loop_count = 0;
                      putchar('.');
                      line_count++;
                      if (line_count >= 50)
                      {
                          putchar('\n');
                          line_count = 0;
                      }
                      fflush(stdout);
                  }
              }
              return 0;
          }
      

      timersub()

      在 Ubuntu 16.04 LTS VM 上,我可以找到包含宏的文件 /usr/include/x86_64-linux-gnu/sys/time.h

      # define timersub(a, b, result)                                               \
        do {                                                                        \
          (result)->tv_sec = (a)->tv_sec - (b)->tv_sec;                             \
          (result)->tv_usec = (a)->tv_usec - (b)->tv_usec;                          \
          if ((result)->tv_usec < 0) {                                              \
            --(result)->tv_sec;                                                     \
            (result)->tv_usec += 1000000;                                           \
          }                                                                         \
        } while (0)
      

      我能看到的所有迹象都是tv_usec 是一个__u32,一个无符号数。 如果是这样,那么&lt; 0 条件永远不会为真,你有时可能而是看到异常大的正值。当然是 YMMV。

      连我的里程数都不一样

      进一步审查表明,虽然有些标头似乎使用__u32 代替tv_usec,但它们并不是主要的系统标头。

      /usr/include/linux/time.h:  __kernel_suseconds_t     tv_usec;   /* microseconds */
      /usr/include/linux/can/bcm.h:   long tv_usec;
      /usr/include/drm/exynos_drm.h:  __u32           tv_usec;
      /usr/include/drm/exynos_drm.h:  __u32           tv_usec;
      /usr/include/drm/vmwgfx_drm.h:  uint32_t tv_usec;
      /usr/include/drm/drm.h: __u32 tv_usec;
      /usr/include/rpc/auth_des.h:    uint32_t tv_usec;           /* Microseconds.  */
      /usr/include/valgrind/vki/vki-darwin.h:#define vki_tv_usec tv_usec
      /usr/include/valgrind/vki/vki-linux.h:  vki_suseconds_t tv_usec;    /* microseconds */
      /usr/include/rpcsvc/rstat.x:    unsigned int tv_usec;   /* and microseconds */
      /usr/include/rpcsvc/rstat.h:    u_int tv_usec;
      /usr/include/x86_64-linux-gnu/bits/utmpx.h:    __int32_t tv_usec;       /* Microseconds.  */
      /usr/include/x86_64-linux-gnu/bits/time.h:    __suseconds_t tv_usec;    /* Microseconds.  */
      /usr/include/x86_64-linux-gnu/bits/utmp.h:    int32_t tv_usec;      /* Microseconds.  */
      /usr/include/x86_64-linux-gnu/sys/time.h:   (ts)->tv_nsec = (tv)->tv_usec * 1000;                           \
      /usr/include/x86_64-linux-gnu/sys/time.h:   (tv)->tv_usec = (ts)->tv_nsec / 1000;                           \
      /usr/include/x86_64-linux-gnu/sys/time.h:# define timerisset(tvp)   ((tvp)->tv_sec || (tvp)->tv_usec)
      /usr/include/x86_64-linux-gnu/sys/time.h:# define timerclear(tvp)   ((tvp)->tv_sec = (tvp)->tv_usec = 0)
      /usr/include/x86_64-linux-gnu/sys/time.h:   ((a)->tv_usec CMP (b)->tv_usec) :                         \
      /usr/include/x86_64-linux-gnu/sys/time.h:    (result)->tv_usec = (a)->tv_usec + (b)->tv_usec;                 \
      /usr/include/x86_64-linux-gnu/sys/time.h:    if ((result)->tv_usec >= 1000000)                        \
      /usr/include/x86_64-linux-gnu/sys/time.h:   (result)->tv_usec -= 1000000;                         \
      /usr/include/x86_64-linux-gnu/sys/time.h:    (result)->tv_usec = (a)->tv_usec - (b)->tv_usec;                 \
      /usr/include/x86_64-linux-gnu/sys/time.h:    if ((result)->tv_usec < 0) {                         \
      /usr/include/x86_64-linux-gnu/sys/time.h:      (result)->tv_usec += 1000000;                          \
      

      对于具有该名称的成员使用无符号类型的任何代码令人担忧,但这并不意味着使用 struct timevaltimersub() 的代码会发生这种情况。

      这段代码:

      #include <sys/time.h>
      #include <stdio.h>
      
      int main(void)
      {
          struct timeval t = { .tv_sec = 0, .tv_usec = -1 };
          printf("%ld %ld\n", (long)t.tv_sec, (long)t.tv_usec);
          return 0;
      }
      

      为 64 位编译(所以 long 足够大,可以容纳 tv_usec 可以定义为的任何东西)打印 0 -1 应该。可以将tv_usec 成员初始化为0,将其递减,并验证它是否为负,以及其他各种相关测试。

      所以,问题并不像“timersub() 错了”那么简单——这是一种巨大的解脱。

      【讨论】:

      • 您需要了解平台的哪些细节?
      • 嗯,它的种类(Linux, Solaris, HP-UX, macOS, Mac OS X, AIX, Windows),大概是它的版本,硬件,库版本等等。基本上不可能您在gettimeofday() 中遇到了错误,因此您有责任通过打印值并显示打印值的代码来验证您在某个系统上看到tv_usec &gt;= 1000000 的声明。或者,通过在timersub() 中显示代码(如果您编写的话)(但原始数据将比计算函数的输出更有说服力),或者......基本上,目前,您的论点纯属轶事。跨度>
      • @Fire3galaxy Linux 发行版,Linux 内核版本 (cat /proc/sys/version),glibc 版本。
      • @JonathanLeffer,这很公平。现在,我无法提供这些细节,因为它不是我的机器(我需要离开)。我可以显示我看到的输出,但我认为在这一点上,我会考虑关闭案例(现在似乎无法重现它)。
      • @Fire3galaxy 众所周知,时间相关的代码很难正确处理,并且有一些令人着迷的细微之处。如果您在返回时仍然遇到问题,请随时联系我们。
      猜你喜欢
      • 1970-01-01
      • 2010-09-05
      • 2014-05-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-20
      • 2012-02-12
      相关资源
      最近更新 更多