Jas*_*onN 7 linux optimization performance benchmarking timing
我想要clock_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 <time.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <x86intrin.h>
// compiled gcc -Wall -O3 -o clockt clockt.cpp
// called glockt sleeptime trials loops
unsigned long long 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性能计数器来测量这一点)。但是,当睡眠时间大于零秒时,此停顿时间会变得明显更长,因为操作系统将调度其他一些线程在同一核心上运行,并且该线程将使用不同的指令和数据。这解释了为什么当你睡觉时,clock_gettime需要更多的时间来执行。
现在关于第二次及以后的测量。从问题来看:
42467
1114
1077
496
455
我在我的系统上观察到,第二个测量值不一定大于后来的测量值。我相信这在您的系统上也是如此。事实上,当你睡10秒或1秒时似乎就是这种情况。在外循环中,这两个函数now包含printf数千条动态指令,并且它们还访问L1数据缓存。您在第二次和以后的测量之间看到的变化是可重现的。所以它是函数本身固有的。请注意,指令本身的执行时间rdtscp可能会相差 4 个周期。另请参阅此。
实际上,clock_gettime当所需精度最多为一百万个周期时,这是有用的。否则,可能会产生误导。
| 归档时间: | 
 | 
| 查看次数: | 610 次 | 
| 最近记录: |