Linux 为什么时钟变得如此不稳定? 简介

Linux 为什么时钟变得如此不稳定? 简介,linux,time,profiling,Linux,Time,Profiling,旧问题部分包含初始问题(此后添加了进一步的调查和结论) 跳到下面的“进一步调查”部分,详细比较不同的计时方法(rdtsc、clock_gettime和QueryThreadCycleTime) 我相信CGT的不稳定行为可以归因于有缺陷的内核或有缺陷的CPU(见结论部分) 用于测试的代码位于该问题的底部(见附录一节) 抱歉,时间太长了 老问题 简而言之:我使用clock\u gettime来测量许多代码段的执行时间。我在两次运行之间遇到了非常不一致的测量结果。与其他方法相比,该方法具有极高的标

旧问题部分包含初始问题(此后添加了进一步的调查和结论)

  • 跳到下面的“进一步调查”部分,详细比较不同的计时方法(
    rdtsc
    clock_gettime
    QueryThreadCycleTime

  • 我相信CGT的不稳定行为可以归因于有缺陷的内核或有缺陷的CPU(见结论部分)

  • 用于测试的代码位于该问题的底部(见附录一节)

  • 抱歉,时间太长了


  • 老问题 简而言之:我使用
    clock\u gettime
    来测量许多代码段的执行时间。我在两次运行之间遇到了非常不一致的测量结果。与其他方法相比,该方法具有极高的标准偏差(见下文解释)

    问题:与其他方法相比,
    clock\u gettime
    会给出如此不一致的测量结果,有什么原因吗?是否有一种具有相同分辨率的替代方法来解释线程空闲时间

    解释:我试图分析C代码的一些小部分。每个代码段的执行时间不超过几微秒。在一次运行中,每个代码段将执行数百次,这将产生
    运行×数百次测量

    我还必须只测量线程实际执行的时间(这就是
    rdtsc
    不合适的原因)。我还需要高分辨率(这就是为什么
    times
    不合适的原因)

    我尝试了以下方法:

    • rdtsc
      (在Linux和Windows上)

    • clock\u gettime
      (在Linux上带有“clock\u THREAD\u CPUTIME\u ID”),以及

    • QueryThreadCycleTime
      (在Windows上)

    方法学:分析进行了25次。在每次运行中,单独的代码段重复101次。因此,我有2525次测量。然后我查看测量值的直方图,并计算一些基本数据(如平均值、标准偏差、中值、模式、最小值和最大值)

    我没有说明我是如何衡量这三种方法的“相似性”的,但这仅仅涉及到对每个代码段花费的时间比例的基本比较(“比例”表示时间是标准化的)。然后我看看这些比例的纯粹差异。这一比较表明,当在25次运行中取平均值时,所有“rdtsc”、“QTCT”和“CGT”测量的比例相同。然而,下面的结果表明,“CGT”具有非常大的标准偏差。这使得它在我的用例中无法使用

    结果

    相同代码段的
    clock_gettime
    rdtsc
    比较(25次101次测量=2525次读数):

    • 时钟时间

      • 1881次11纳秒的测量
      • 595个测量值(几乎正态分布)在3369和3414纳秒之间
      • 2次11680纳秒的测量
      • 1测量1506022纳秒,以及
      • 其余的在900到5000纳秒之间

      • 最小:11纳秒

      • 最大值:1506022纳秒
      • 平均值:1471.862纳秒
      • 中位数:11纳秒
      • 模式:11纳秒
      • 标准差饷租值:29991.034
    • rdtsc(注意:此运行过程中未发生上下文切换,但如果发生,通常只会产生30000个左右的单个测量值):

      • 1178个测量值介于274和325个刻度之间
      • 306个测量值介于326和375个刻度之间
      • 910个测量值介于376和425个刻度之间
      • 129个测量值介于426和990个刻度之间
      • 1测量1240个滴答声,以及
      • 1测量1256个蜱

      • 最小:274蜱

      • 最大值:1256蜱
      • 平均值:355.806蜱
      • 中位数:333蜱
      • 模式:376滴答声
      • 标准差饷租值:83.896
    讨论

    • rdtsc
      在Linux和Windows上给出了非常相似的结果。它有一个可接受的标准偏差——它实际上相当一致/稳定。但是,它不考虑线程空闲时间。因此,上下文切换会使测量结果变得不稳定(在Windows上,我经常观察到这一点:平均为1000个左右的代码段会时不时地占用30000个左右的时间,这肯定是因为抢占)

    • QueryThreadCycleTime
      提供了非常一致的测量结果,即与
      rdtsc
      相比,标准偏差要低得多。当没有发生上下文切换时,此方法几乎与
      rdtsc
      相同

    • 另一方面,clock_gettime
    产生了极不一致的结果(不仅在运行之间,而且在测量之间)。标准偏差是极端的(与
    rdtsc
    相比)

    我希望统计数字没问题。但是,这两种方法的测量结果如此不一致的原因是什么?当然,还有缓存、CPU/核心迁移等。但这些都不应该对“rdtsc”和“clock_gettime”之间的任何差异负责。发生了什么事


    进一步调查 我对此做了进一步的调查。我做了两件事:

  • 测量了仅调用
    clock\u gettime(clock\u THREAD\u CPUTIME\u ID,&t)
    的开销(参见附录中的代码1),以及

  • 在一个名为
    clock\u gettime
    的普通循环中,将读数存储到一个数组中(参见附录中的代码2)。我测量增量时间(连续测量时间的差异,这应该与
    时钟的调用开销相对应)
    
          Range       |  Frequency
    ------------------+-----------
      697 < x ≤ 800   ->     78111  <-- cached?
      800 < x ≤ 1000  ->     16412
     1000 < x ≤ 1500  ->         3
     1500 < x ≤ 2000  ->      4836  <-- uncached?
     2000 < x ≤ 3000  ->       305
     3000 < x ≤ 5000  ->       161
     5000 < x ≤ 10000 ->       105
    10000 < x ≤ 15000 ->        53
    15000 < x ≤ 20000 ->         8
    20000 < x         ->         5
    
          Range         |  Frequency
    --------------------+-----------
              x ≤ 1     ->     86738  <-- cached?
        282 < x ≤ 300   ->     13118  <-- uncached?
        300 < x ≤ 440   ->        78
       2000 < x ≤ 5000  ->        52
       5000 < x ≤ 30000 ->         5
    3000000 < x         ->         8
    
          Range       |  Frequency
    ------------------+-----------
       34 < x ≤ 35    ->     16240  <-- cached?
       41 < x ≤ 42    ->     63585  <-- uncached? (small difference)
       48 < x ≤ 49    ->     19779  <-- uncached?
       49 < x ≤ 120   ->       195
     3125 < x ≤ 5000  ->       144
     5000 < x ≤ 10000 ->        45
    10000 < x ≤ 20000 ->         9
    20000 < x         ->         2
    
          Range       |  Frequency
    ------------------+-----------
       13 < x ≤ 14    ->       192
       14 < x ≤ 21    ->     78172  <-- cached?
       21 < x ≤ 50    ->     10818
       50 < x ≤ 103   ->     10624  <-- uncached?
     5825 < x ≤ 6500  ->        88
     6500 < x ≤ 8000  ->        88
     8000 < x ≤ 10000 ->        11
    10000 < x ≤ 15000 ->         4
    15000 < x ≤ 16372 ->         2
    
          Range       |  Frequency
    ------------------+-----------
      879 < x ≤ 890   ->     71347  <-- cached?
      895 < x ≤ 1469  ->       844
     1469 < x ≤ 1600  ->     27613  <-- uncached?
     1600 < x ≤ 2000  ->        55
     2000 < x ≤ 4000  ->        86
     4000 < x ≤ 8000  ->        43
     8000 < x ≤ 16000 ->        10
    16000 < x         ->         1
    
    #include <time.h>
    #include <stdio.h>
    #include <stdint.h>
    
    /* Compiled & executed with:
    
        gcc clock_gettime_overhead.c -O0 -lrt -o clock_gettime_overhead
        ./clock_gettime_overhead 100000
    */
    
    int main(int argc, char **args) {
        struct timespec tstart, tend, dummy;
        int n, N;
        N = atoi(args[1]);
        clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tstart);
        for (n = 0; n < N; ++n) {
            clock_gettime(CLOCK_THREAD_CPUTIME_ID, &dummy);
            clock_gettime(CLOCK_THREAD_CPUTIME_ID, &dummy);
            clock_gettime(CLOCK_THREAD_CPUTIME_ID, &dummy);
            clock_gettime(CLOCK_THREAD_CPUTIME_ID, &dummy);
            clock_gettime(CLOCK_THREAD_CPUTIME_ID, &dummy);
            clock_gettime(CLOCK_THREAD_CPUTIME_ID, &dummy);
            clock_gettime(CLOCK_THREAD_CPUTIME_ID, &dummy);
            clock_gettime(CLOCK_THREAD_CPUTIME_ID, &dummy);
            clock_gettime(CLOCK_THREAD_CPUTIME_ID, &dummy);
            clock_gettime(CLOCK_THREAD_CPUTIME_ID, &dummy);
        }
        clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tend);
        printf("Estimated overhead: %lld ns\n",
                ((int64_t) tend.tv_sec * 1000000000 + (int64_t) tend.tv_nsec
                        - ((int64_t) tstart.tv_sec * 1000000000
                                + (int64_t) tstart.tv_nsec)) / N / 10);
        return 0;
    }
    
    #include <time.h>
    #include <stdio.h>
    #include <stdint.h>
    
    /* Compiled & executed with:
    
        gcc clock_gettime_delta.c -O0 -lrt -o clock_gettime_delta
        ./clock_gettime_delta > results
    */
    
    #define N 100000
    
    int main(int argc, char **args) {
        struct timespec sample, results[N];
        int n;
        for (n = 0; n < N; ++n) {
            clock_gettime(CLOCK_THREAD_CPUTIME_ID, &sample);
            results[n] = sample;
        }
        printf("%s\t%s\n", "Absolute time", "Delta");
        for (n = 1; n < N; ++n) {
            printf("%lld\t%lld\n",
                   (int64_t) results[n].tv_sec * 1000000000 + 
                       (int64_t)results[n].tv_nsec,
                   (int64_t) results[n].tv_sec * 1000000000 + 
                       (int64_t) results[n].tv_nsec - 
                       ((int64_t) results[n-1].tv_sec * 1000000000 + 
                            (int64_t)results[n-1].tv_nsec));
        }
        return 0;
    }
    
    static uint64_t rdtsc() {
    #if defined(__GNUC__)
    #   if defined(__i386__)
        uint64_t x;
        __asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
        return x;
    #   elif defined(__x86_64__)
        uint32_t hi, lo;
        __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
        return ((uint64_t)lo) | ((uint64_t)hi << 32);
    #   else
    #       error Unsupported architecture.
    #   endif
    #elif defined(_MSC_VER)
        return __rdtsc();
    #else
    #   error Other compilers not supported...
    #endif
    }
    
    #include <stdio.h>
    #include <stdint.h>
    #include "rdtsc.h"
    
    /* Compiled & executed with:
    
        gcc rdtsc_delta.c -O0 -o rdtsc_delta
        ./rdtsc_delta > rdtsc_delta_results
    
    Windows:
    
        cl -Od rdtsc_delta.c
        rdtsc_delta.exe > windows_rdtsc_delta_results
    */
    
    #define N 100000
    
    int main(int argc, char **args) {
        uint64_t results[N];
        int n;
        for (n = 0; n < N; ++n) {
            results[n] = rdtsc();
        }
        printf("%s\t%s\n", "Absolute time", "Delta");
        for (n = 1; n < N; ++n) {
            printf("%lld\t%lld\n", results[n], results[n] - results[n-1]);
        }
        return 0;
    }
    
    #include <time.h>
    #include <stdio.h>
    #include <stdint.h>
    #include "rdtsc.h"
    
    /* Compiled & executed with:
    
        gcc rdtsc_overhead.c -O0 -lrt -o rdtsc_overhead
        ./rdtsc_overhead 1000000 > rdtsc_overhead_results
    
    Windows:
    
        cl -Od rdtsc_overhead.c
        rdtsc_overhead.exe 1000000 > windows_rdtsc_overhead_results
    */
    
    int main(int argc, char **args) {
        uint64_t tstart, tend, dummy;
        int n, N;
        N = atoi(args[1]);
        tstart = rdtsc();
        for (n = 0; n < N; ++n) {
            dummy = rdtsc();
            dummy = rdtsc();
            dummy = rdtsc();
            dummy = rdtsc();
            dummy = rdtsc();
            dummy = rdtsc();
            dummy = rdtsc();
            dummy = rdtsc();
            dummy = rdtsc();
            dummy = rdtsc();
        }
        tend = rdtsc();
        printf("%G\n", (double)(tend - tstart)/N/10);
        return 0;
    }
    
    #include <stdio.h>
    #include <stdint.h>
    #include <Windows.h>
    
    /* Compiled & executed with:
    
        cl -Od qtct_delta.c
        qtct_delta.exe > windows_qtct_delta_results
    */
    
    #define N 100000
    
    int main(int argc, char **args) {
        uint64_t ticks, results[N];
        int n;
        for (n = 0; n < N; ++n) {
            QueryThreadCycleTime(GetCurrentThread(), &ticks);
            results[n] = ticks;
        }
        printf("%s\t%s\n", "Absolute time", "Delta");
        for (n = 1; n < N; ++n) {
            printf("%lld\t%lld\n", results[n], results[n] - results[n-1]);
        }
        return 0;
    }
    
    #include <stdio.h>
    #include <stdint.h>
    #include <Windows.h>
    
    /* Compiled & executed with:
    
        cl -Od qtct_overhead.c
        qtct_overhead.exe 1000000
    */
    
    int main(int argc, char **args) {
        uint64_t tstart, tend, ticks;
        int n, N;
        N = atoi(args[1]);
        QueryThreadCycleTime(GetCurrentThread(), &tstart);
        for (n = 0; n < N; ++n) {
            QueryThreadCycleTime(GetCurrentThread(), &ticks);
            QueryThreadCycleTime(GetCurrentThread(), &ticks);
            QueryThreadCycleTime(GetCurrentThread(), &ticks);
            QueryThreadCycleTime(GetCurrentThread(), &ticks);
            QueryThreadCycleTime(GetCurrentThread(), &ticks);
            QueryThreadCycleTime(GetCurrentThread(), &ticks);
            QueryThreadCycleTime(GetCurrentThread(), &ticks);
            QueryThreadCycleTime(GetCurrentThread(), &ticks);
            QueryThreadCycleTime(GetCurrentThread(), &ticks);
            QueryThreadCycleTime(GetCurrentThread(), &ticks);
        }
        QueryThreadCycleTime(GetCurrentThread(), &tend);
        printf("%G\n", (double)(tend - tstart)/N/10);
        return 0;
    }