微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

linux – 为什么clock_gettime(CLOCK_REALTIME,..)的调用延迟变化如此之大?

我正在尝试time_gettime(CLOCK_REALTIME,…)调用多长时间. “回到当天”我曾经在循环的顶部称它为一次,因为它是一个相当昂贵的电话.但是现在,我希望通过vDSO和一些时钟改进,它可能不会那么慢.

我编写了一些测试代码,使用__rdtscp来重复调用clock_gettime(rdtscp调用绕过一个调用clock_gettime的循环并将结果添加到一起,这样编译器就不会进行太多的优化).

如果我快速连续调用clock_gettime(),则时间长度从大约45k时钟周期下降到500个周期.其中一些我认为可能是第一次调用必须加载vDSO代码(仍然没有完全对我有意义),但如何需要一些调用来获得500我根本无法解释,这种行为似乎无论我如何测试它都是恒定的:

42467
1114
1077
496
455

但是,如果我在调用clock_gettime之间休眠(一秒或十分,无关紧要),它只会达到约4.7k周期的稳定状态:

这里睡10秒钟:

28293
1093
4729
4756
4736

这里睡1秒钟:

61578
855
4753
4741
5645
4753
4732

缓存行为似乎无法描述这一点(在桌面系统上没有做太多任何事情).我应该为clock_gettime的调用预算多少钱?为什么呼叫变得越来越快?为什么睡一小段时间这么重要?

tl; dr我试图理解调用clock_gettime所花费的时间(CLOCK_REALTIME,…)不明白为什么它在快速连续调用时运行得更快而不是在调用间的第二次调用.

更新:这是proc 0上的cpuinfo

processor   : 0
vendor_id   : GenuineIntel
cpu family  : 6
model       : 158
model name  : Intel(R) Core(TM) i7-7700HQ cpu @ 2.80GHz
stepping    : 9
microcode   : 0x84
cpu MHz     : 2800.000
cache size  : 6144 KB
physical id : 0
siblings    : 8
core id     : 0
cpu cores   : 4
apicid      : 0
initial apicid  : 0
fpu     : yes
fpu_exception   : yes
cpuid level : 22
wp      : yes
flags       : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_kNown_freq pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dNowprefetch cpuid_fault epb intel_pt tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid mpx rdseed adx smap clflushopt xsaveopt xsavec xgetbv1 xsaves dtherm ida arat pln pts hwp hwp_notify hwp_act_window hwp_epp
bugs        :
bogomips    : 5616.00
clflush size    : 64
cache_alignment : 64
address sizes   : 39 bits physical,48 bits virtual
power management:

这是重新创建的测试代码

#include dio.h>
#include Now() {
    struct timespec s;
    clock_gettime(CLOCK_REALTIME,&s);
    return (s.tv_sec * 1000000000ull) + s.tv_nsec;
}

int main(int argc,char **argv) {
    int sleeptime = atoi(argv[1]);
    int trials = atoi(argv[2]);
    int loops = atoi(argv[3]);

    unsigned long long x,y,n = 0;
    unsigned int d;


    x = __rdtscp(&d);
    n = Now();
    asm volatile("": "+r" (n));
    y = __rdtscp(&d);

    printf("init run %lld\n",(y-x));

    for(int t = 0; t < trials; ++t) {
        if(sleeptime > 0) sleep(sleeptime);
        x = __rdtscp(&d);
        for(int l = 0; l < loops; ++l) {
            n = Now();
            asm volatile("": "+r" (n));
        }
        y = __rdtscp(&d);
        printf("trial %d took %lld\n",t,(y-x));
    }

    exit(0);
}
最佳答案
第一次调用clock_gettime时,页面上会发生页面错误,其中包含该函数的指令.在我的系统上,这是一个页面错误,需要几千个周期来处理(最多10,000个周期).我的cpu运行在3.4GHz.我认为您的cpu运行频率要低得多,因此在系统上处理页面错误会花费更多时间.但这里的重点是,对clock_gettime的第一次调用将比后来的调用花费更多的时间,这正是你所观察到的.

代码显示的第二个主要影响是由于指令缓存未命中而导致的重大停顿.您可能看起来只调用两个函数,即Now和printf,但这些函数调用其他函数,它们都在L1指令缓存上竞争.总的来说,它取决于所有这些功能在物理地址空间中的对齐方式.当休眠时间为零秒时,由于指令高速缓存未命中而导致的停止时间实际上相对较小(您可以使用ICACHE.IFETCH_STALL性能计数器来测量它).但是,当睡眠时间大于零秒时,此停顿时间变得非常大,因为OS将调度一些其他线程在同一核心上运行,并且该线程会有不同的指令和数据.这解释了为什么当你睡觉时,clock_gettime需要更多的时间来执行.

现在关于第二次和以后的测量.从问题:

42467
1114
1077
496
455

我在我的系统上观察到第二次测量不一定比后来的测量更大.我相信你的系统也是如此.事实上,当您睡眠10秒或1秒时,情况似乎就是这样.在外部循环中,两个函数现在和printf包含数千个动态指令,它们也访问L1数据高速缓存.您在第二次和后续测量之间看到的可变性是可重现的.所以它是功能本身所固有的.请注意,rdtscp指令本身的执行时间可能会有4个周期.另见this.

实际上,当期望的精度最多为一百万个周期时,clock_gettime很有用.否则,它可能会产生误导.

原文地址:https://www.jb51.cc/linux/440988.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐