clock_gettime()是否适合亚微秒时序?

时间:2011-10-28 22:27:31

标签: linux performance ubuntu profiling

在我们的应用程序的Linux版本中,我需要一个用于嵌入式探查器的高分辨率计时器。我们的分析器测量的范围与单个函数一样小,因此它需要一个优于25纳秒的定时器精度。

以前我们的实现使用内联汇编和rdtsc操作来直接从CPU查询高频定时器,但是this is problematic并且需要经常重新校准。

所以我尝试使用clock_gettime函数来查询CLOCK_PROCESS_CPUTIME_ID。文档声称这给了我纳秒的时间,但我发现单次调用clock_gettime()的开销超过250ns。这使得不可能将事件计时100ns,并且在计时器功能上具有如此高的开销会严重降低应用程序性能,从而扭曲配置文件超出值。 (我们每秒有数十万个分析节点。)

有没有办法调用clock_gettime()开销少于¼μs?或者是否有其他方法可以可靠地获得时间戳计数器的开销<25ns?还是我坚持使用rdtsc

以下是我用来计算时间clock_gettime()的代码。

// calls gettimeofday() to return wall-clock time in seconds:
extern double Get_FloatTime();
enum { TESTRUNS = 1024*1024*4 };

// time the high-frequency timer against the wall clock
{
    double fa = Get_FloatTime();
    timespec spec; 
    clock_getres( CLOCK_PROCESS_CPUTIME_ID, &spec );
    printf("CLOCK_PROCESS_CPUTIME_ID resolution: %ld sec %ld nano\n", 
            spec.tv_sec, spec.tv_nsec );
    for ( int i = 0 ; i < TESTRUNS ; ++ i )
    {
        clock_gettime( CLOCK_PROCESS_CPUTIME_ID, &spec );
    }
    double fb = Get_FloatTime();
    printf( "clock_gettime %d iterations : %.6f msec %.3f microsec / call\n",
        TESTRUNS, ( fb - fa ) * 1000.0, (( fb - fa ) * 1000000.0) / TESTRUNS );
}
// and so on for CLOCK_MONOTONIC, CLOCK_REALTIME, CLOCK_THREAD_CPUTIME_ID.

结果:

CLOCK_PROCESS_CPUTIME_ID resolution: 0 sec 1 nano
clock_gettime 8388608 iterations : 3115.784947 msec 0.371 microsec / call
CLOCK_MONOTONIC resolution: 0 sec 1 nano
clock_gettime 8388608 iterations : 2505.122119 msec 0.299 microsec / call
CLOCK_REALTIME resolution: 0 sec 1 nano
clock_gettime 8388608 iterations : 2456.186031 msec 0.293 microsec / call
CLOCK_THREAD_CPUTIME_ID resolution: 0 sec 1 nano
clock_gettime 8388608 iterations : 2956.633930 msec 0.352 microsec / call

这是在标准的Ubuntu内核上。该应用程序是Windows应用程序的一个端口(我们的rdtsc内联汇编工作正常)。

附录:

x86-64 GCC是否有一些内在等价于__rdtsc(),所以我至少可以避免内联汇编?

7 个答案:

答案 0 :(得分:7)

没有。您必须使用特定于平台的代码才能执行此操作。在x86和x86-64上,您可以使用'rdtsc'来阅读Time Stamp Counter

只需移植您正在使用的rdtsc程序集。

__inline__ uint64_t rdtsc(void) {
  uint32_t lo, hi;
  __asm__ __volatile__ (      // serialize
  "xorl %%eax,%%eax \n        cpuid"
  ::: "%rax", "%rbx", "%rcx", "%rdx");
  /* We cannot use "=A", since this would use %rax on x86_64 and return only the lower 32bits of the TSC */
  __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
  return (uint64_t)hi << 32 | lo;
}

答案 1 :(得分:6)

我在我的系统上运行了一些基准测试,这是一个四核E5645 Xeon支持一个运行内核3.2.54的恒定TSC,结果是:

clock_gettime(CLOCK_MONOTONIC_RAW)       100ns/call
clock_gettime(CLOCK_MONOTONIC)           25ns/call
clock_gettime(CLOCK_REALTIME)            25ns/call
clock_gettime(CLOCK_PROCESS_CPUTIME_ID)  400ns/call
rdtsc (implementation @DavidSchwarz)     600ns/call

所以看起来在一个相当现代的系统上(接受的答案)rdtsc是最糟糕的路线。

答案 2 :(得分:1)

  

在我们的应用程序的Linux版本中,我需要一个用于嵌入式探查器的高分辨率计时器。我们的分析器测量的范围与单个函数一样小,因此它需要一个优于25纳秒的定时器精度。

您是否考虑过oprofileperf?您可以使用CPU上的性能计数器硬件来获取分析数据,而无需向代码本身添加检测。您可以看到每个函数的数据,甚至是每行代码。 “唯一”的缺点是它不会测量消耗的挂钟时间,它会测量消耗的CPU时间,因此不适合所有调查。

答案 3 :(得分:1)

尝试一下clockid_t CLOCK_MONOTONIC_RAW?

CLOCK_MONOTONIC_RAW(自Linux 2.6.28起;特定于Linux)               与CLOCK_MONOTONIC类似,但提供访问权限               基于硬件的原始时间,不受NTP限制               调整或执行的增量调整               的adjtime(3)。

来自Man7.org

答案 4 :(得分:0)

您正在使用控制参数调用clock_getttime,这意味着api通过if-else树分支以查看您想要的时间。我知道你不能通过这个调用避免这种情况,但看看你是否可以深入研究系统代码并调用内核最终直接调用的内容。另外,我注意到你包括循环时间(i ++和条件分支)。

答案 5 :(得分:0)

是的,大多数现代平台都有一个合适的clock_gettime调用,它使用VDSO机制完全在用户空间中实现,并且可以完成20到30纳秒的完成。

在内部,这是使用rdtscrdtscp来计算时间的细粒度部分,加上调整以使其与挂钟时间保持同步(取决于您选择的时钟) )和从您的平台上的任何单位rdtsc转换为纳秒的乘法。

{em} clock_gettime提供的所有时钟都不会实现这种快速方法,并且obvious并不总是https://developers.google.com/admob/android/eu-consent。通常CLOCK_MONOTONIC是一个不错的选择,但你应该在自己的系统上测试

答案 6 :(得分:0)

这就是调用 clock_gettime() 函数时发生的情况。

根据您选择的时钟,它将调用相应的函数。 (来自内核的 vclock_gettime.c 文件)

int clock_gettime(clockid_t, struct __kernel_old_timespec *)
    __attribute__((weak, alias("__vdso_clock_gettime")));

notrace int
__vdso_clock_gettime_stick(clockid_t clock, struct __kernel_old_timespec *ts)
{
    struct vvar_data *vvd = get_vvar_data();

switch (clock) {
case CLOCK_REALTIME:
    if (unlikely(vvd->vclock_mode == VCLOCK_NONE))
        break;
    return do_realtime_stick(vvd, ts);
case CLOCK_MONOTONIC:
    if (unlikely(vvd->vclock_mode == VCLOCK_NONE))
        break;
    return do_monotonic_stick(vvd, ts);
case CLOCK_REALTIME_COARSE:
    return do_realtime_coarse(vvd, ts);
case CLOCK_MONOTONIC_COARSE:
    return do_monotonic_coarse(vvd, ts);
}
/*
 * Unknown clock ID ? Fall back to the syscall.
 */
    return vdso_fallback_gettime(clock, ts);
}

CLOCK_MONITONIC 更好(虽然我使用 CLOCK_MONOTONIC_RAW),因为它不受 NTP 时间调整的影响。


这就是 do_monotonic_stick 在内核中的实现方式:

notrace static __always_inline int do_monotonic_stick(struct vvar_data *vvar,
                              struct __kernel_old_timespec *ts)
{
    unsigned long seq;
    u64 ns;

    do {
        seq = vvar_read_begin(vvar);
        ts->tv_sec = vvar->monotonic_time_sec;
        ns = vvar->monotonic_time_snsec;
        ns += vgetsns_stick(vvar);
        ns >>= vvar->clock.shift;
    } while (unlikely(vvar_read_retry(vvar, seq)));

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

    return 0;
}

提供纳秒分辨率的 vgetsns_stick() 函数实现为:

notrace static __always_inline u64 vgetsns(struct vvar_data *vvar)
{
    u64 v;
    u64 cycles;

    cycles = vread_tick();
    v = (cycles - vvar->clock.cycle_last) & vvar->clock.mask;
    return v * vvar->clock.mult;
}

函数 vread_tick() 根据 CPU 从寄存器读取周期的地方:

notrace static __always_inline u64 vread_tick(void)
{
    register unsigned long long ret asm("o4");

    __asm__ __volatile__("rd %%tick, %L0\n\t"
                 "srlx %L0, 32, %H0"
                 : "=r" (ret));
    return ret;
}

clock_gettime() 的单次调用大约需要 20 到 100 纳秒。读取 rdtsc 寄存器并将周期转换为时间总是更快。

我在这里对 CLOCK_MONOTONIC_RAW 做了一些实验:Unexpected periodic behaviour of an ultra low latency hard real time multi threaded x86 code