C语言 gettimeofday()微秒不限于秒以下

0aydgbwb  于 2022-12-22  发布在  其他
关注(0)|答案(3)|浏览(154)

当我输出gettimeofday()的微秒字段时,我注意到微秒字段大于1,000,000。有人知道这是为什么吗?这是否意味着我对gettimeofday()的解释是错误的?
为了记录在案,我的假设是根据gettimeofday()的当前时间(以微秒为单位)如下:

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

编辑:下面是导致问题的代码。在下面的注解之后,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);
8fq7wneg

8fq7wneg1#

成功到gettimeofday后,是的,tv_usec保证严格小于1000000。
如果你(认为你)看到了一个1000000或更大的值,那么是的,很可能你做错了什么。
一个常见的错误是天真地加减两个struct timeval值,而没有在tv_sectv_usec字段之间实现正确的进位或借位,这很容易导致tv_usec中的(错误)值大于1000000。(在你编辑过的帖子中,你提到了减去时间规格,但是你使用的是系统提供的timersub函数,它应该得到正确的借用。)
如果你用struct timespec而不是struct timeval,* 如果 * 一个闰秒正在进行,* 如果 * 你(奇迹般地)使用一个实现了MarkusKuhn在https://www.cl.cam.ac.uk/~mgk25/posix-clocks.html中提出的CLOCK_UTC时钟类型的OS内核,您会看到tv_nsec值大于100000000,但这需要很多“如果“(据我所知,没有一个广泛使用的内核实现过CLOCK_UTC)。

yyyllmsg

yyyllmsg2#

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

#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;
    }

一米一分一秒

在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,如果是这样,那么< 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()是错误的"那么简单--这是一个巨大的解脱。

thtygnil

thtygnil3#

您的printf可疑,可能导致此问题。
%zu用于打印size_t值。但是tv_sectv_usec都没有size_t类型。
在现代系统中,size_t可能是64位,但如果tv_sectv_usec不是,printf最终将错误地打印这些值。
我把你的printf改成了

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的特定选择。
像这样打印实现定义类型的值的通用和可移植方法是将其显式转换为最大类型,然后使用与转换到的类型对应的printf。例如:

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

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

强制类型转换可能是也可能不是no-op,但关键是,无论原始类型是什么,最终得到的结果都符合您告诉printf的预期。

相关问题