Linux 即使使用VDSO,时钟时间也可能非常慢

Linux 即使使用VDSO,时钟时间也可能非常慢,linux,time-measurement,vdso,Linux,Time Measurement,Vdso,我正在英特尔(R)至强(R)CPU E5-2667 v4@3.20GHz上使用CentOS Linux 7.3.1611版 在测试我的用户空间应用程序时,我注意到clock_gettime(clock_MONOTONIC,&ts)可能需要5-6微秒,而不是平均23纳秒。它可能仅在每10000个后续调用中发生一次,但也可能发生 如果没有VDSO库,可以对其进行解释。然而,VDSO用于每个时钟(我通过strace检查) 不管对应的线程是否与某个CPU内核关联。不管这个CPU内核是否与操作系统隔离。这

我正在英特尔(R)至强(R)CPU E5-2667 v4@3.20GHz上使用CentOS Linux 7.3.1611版

在测试我的用户空间应用程序时,我注意到clock_gettime(clock_MONOTONIC,&ts)可能需要5-6微秒,而不是平均23纳秒。它可能仅在每10000个后续调用中发生一次,但也可能发生

如果没有VDSO库,可以对其进行解释。然而,VDSO用于每个时钟(我通过strace检查)

不管对应的线程是否与某个CPU内核关联。不管这个CPU内核是否与操作系统隔离。这意味着测试应用程序可能运行在专用CPU内核上,而延迟可能会出现

我通过比较两个随后的clock_gettime调用的结果来测量延迟,如:

unsigned long long __gettimeLatencyNs() {
    struct timespec t1_ts;
    struct timespec t2_ts;
    clock_gettime(CLOCK_MONOTONIC, &t1_ts);
    clock_gettime(CLOCK_MONOTONIC, &t2_ts);
    return ((t2_ts.tv_sec - t1_ts.tv_sec)*NANO_SECONDS_IN_SEC + t2_ts.tv_nsec - t1_ts.tv_nsec);
}  

有人能分享一些想法吗?有什么问题吗?

让我们看一下源代码:

我们在这里看到的是代码在循环中运行。此循环使用
不太可能的
条件进行注释。这种情况与以下事实有关:此代码读取有时会更新的共享内存,并且在更新时,代码需要等待更新完成


那么,对您的问题最有可能的答案是,在相应的内核代码更新其结构时,您经常捕获
clock\u gettime
。当这种情况发生时,代码运行速度明显变慢。

我不认为是
clock\u gettime
调用本身的逻辑周期性地花费了更长的时间,而是您的定时循环周期性地被中断,而这额外的时间显示为一个额外的长间隔

也就是说,任何类型的定时循环都会受到外部事件(如中断)的干扰。例如,除了使用非常特定的无滴答的内核配置(不是默认配置)外,应用程序将定期被时钟中断中断,这将进行一些处理,以查看是否应该运行另一个进程。即使最终没有其他进程运行,这也很容易解释为几微秒

此外,硬件可能由于各种原因而暂时暂停,例如当其他内核进入或离开空闲状态时发生的原因。我在8微秒左右测量了这些转变,接近您报告的值。在这些暂停期间,CPU不执行指令,但保持运行,因此显示为超长的间隔

除此之外,还有一个原因可以解释为什么会出现异常计时。这个答案还包括,如果你感兴趣,你可以缩小可能的原因


最后,答案建议当内核更新数据结构时,
clock\u gettime
本身可能被阻塞。虽然这当然是可能的,但我认为这比其他原因的可能性要小。您可以复制并粘贴VDSO代码,然后修改它以记录是否确实发生了任何阻塞,并调用它以查看暂停是否与阻塞相关。我想不会。

似乎,我们不仅在这样的综合测试中,还会遇到“内核正在更新某些东西”的等待。如果内核只需要几微秒来更新内存,那就很奇怪了。。。
/* Code size doesn't matter (vdso is 4k anyway) and this is faster. */
notrace static int __always_inline do_realtime(struct timespec *ts)
{
    unsigned long seq;
    u64 ns;
    int mode;

    do {
        seq = gtod_read_begin(gtod);
        mode = gtod->vclock_mode;
        ts->tv_sec = gtod->wall_time_sec;
        ns = gtod->wall_time_snsec;
        ns += vgetsns(&mode);
        ns >>= gtod->shift;
    } while (unlikely(gtod_read_retry(gtod, seq)));

    ts->tv_sec += __iter_div_u64_rem(ns, NSEC_PER_SEC, &ns);
    ts->tv_nsec = ns;

    return mode;
}