Perf overcounting simple CPU-bound 循环:神秘的内核工作?

Perf overcounting simple CPU-bound loop: mysterious kernel work?

我已经使用 Linux perf 一段时间来进行应用程序分析。通常分析的应用程序相当复杂,因此人们倾向于简单地按表面值获取报告的计数器值,只要与您基于第一个预期的值没有任何 gross 差异原则。

然而,最近我分析了一些简单的 64 位汇编程序 - 足够简单到几乎可以精确计算各种计数器的预期值,而且 perf stat 似乎多算了。

以下面的循环为例:

.loop:
    nop
    dec rax
    nop
    jne .loop

这将简单地循环 n 次,其中 nrax 的初始值。循环的每次迭代执行 4 条指令,因此您会期望执行 4 * n 条指令,加上一些用于进程启动和终止的小的固定开销以及在进入循环之前设置 n 的一小段代码。

这是 n = 1,000,000,000 的(典型)perf stat 输出:

~/dev/perf-test$ perf stat ./perf-test-nop 1

 Performance counter stats for './perf-test-nop 1':

        301.795151      task-clock (msec)         #    0.998 CPUs utilized          
                 0      context-switches          #    0.000 K/sec                  
                 0      cpu-migrations            #    0.000 K/sec                  
                 2      page-faults               #    0.007 K/sec                  
     1,003,144,430      cycles                    #    3.324 GHz                      
     4,000,410,032      instructions              #    3.99  insns per cycle        
     1,000,071,277      branches                  # 3313.742 M/sec                  
             1,649      branch-misses             #    0.00% of all branches        

       0.302318532 seconds time elapsed

嗯。我们看到的不是大约 4,000,000,000 条指令和 1,000,000,000 条分支,而是神秘的额外 410,032 条指令和 71,277 条分支。总是有 "extra" 条指令,但数量略有不同 - 例如,后续的 运行s 分别有 421K、563K 和 464K extra 条指令。您可以通过构建我的 simple github project 在您的系统上自己 运行。

好的,所以你可能会猜到这几十万个额外的指令只是固定的应用程序设置和拆卸成本(用户空间设置是 very small,但可能有隐藏的东西)。让我们尝试 n=10 billion 然后:

~/dev/perf-test$ perf stat ./perf-test-nop 10

 Performance counter stats for './perf-test-nop 10':

       2907.748482      task-clock (msec)         #    1.000 CPUs utilized          
                 3      context-switches          #    0.001 K/sec                  
                 0      cpu-migrations            #    0.000 K/sec                  
                 2      page-faults               #    0.001 K/sec                  
    10,012,820,060      cycles                    #    3.443 GHz                    
    40,004,878,385      instructions              #    4.00  insns per cycle        
    10,001,036,040      branches                  # 3439.443 M/sec                  
             4,960      branch-misses             #    0.00% of all branches        

       2.908176097 seconds time elapsed

现在有大约 490 万条 额外的 指令,比以前增加了大约 10 倍,与循环计数增加 10 倍成正比。

您可以尝试各种计数器 - 所有 CPU 相关的计数器都显示类似的比例增加。让我们关注指令计数以保持简单。使用 :u:k 后缀分别测量 userkernel 计数,表明在 kernel 几乎占了所有的额外事件:

~/dev/perf-test$ perf stat -e instructions:u,instructions:k ./perf-test-nop 1

 Performance counter stats for './perf-test-nop 1':

     4,000,000,092      instructions:u                                              
           388,958      instructions:k                                              

       0.301323626 seconds time elapsed

宾果游戏。在这 389,050 条额外指令中,足足有 99.98% (388,958) 发生在内核中。

好的,但是那会给我们留下什么?这是一个简单的 CPU 绑定循环。它不进行任何系统调用,也不访问内存(这可能通过缺页机制间接调用内核)。为什么内核会代表我的应用程序执行指令?

它似乎不是由上下文切换或 CPU 迁移引起的,因为它们处于或接近于零,并且在任何情况下 extra 指令计数与发生更多此类事件的 运行 无关。

额外内核指令的数量实际上与循环计数非常平滑。这是(十亿次)循环迭代与内核指令的图表:

您可以看到该关系几乎是完全线性的 - 事实上直到 15e9 次迭代只有一个异常值。在那之后,似乎有两条独立的线,表明对导致多余时间的任何事物进行了某种量化。在任何情况下,在主循环中每执行 1e9 条指令,就会产生大约 350K 条内核指令。

最后,我注意到执行的内核指令数似乎与 运行time1(或 CPU 时间)而不是执行的指令。为了对此进行测试,我使用了 similar program,但其中一个 nop 指令被替换为 idiv,它具有大约 40 个周期的延迟(删除了一些无趣的行):

~/dev/perf-test$ perf stat ./perf-test-div 10

 Performance counter stats for './perf-test-div 10':

    41,768,314,396      cycles                    #    3.430 GHz                       
     4,014,826,989      instructions              #    0.10  insns per cycle        
     1,002,957,543      branches                  #   82.369 M/sec                  

      12.177372636 seconds time elapsed

这里我们用了大约 42e9 个周期来完成 1e9 次迭代,并且我们有大约 14,800,000 条额外指令。相比之下,对于具有 nop 的相同 1e9 循环,只有大约 400,000 条额外指令。如果我们与需要大约相同数量 cycles(40e9 次迭代)的 nop 循环进行比较,我们会看到几乎完全相同数量的额外指令:

~/dev/perf-test$ perf stat ./perf-test-nop 41

 Performance counter stats for './perf-test-nop 41':

    41,145,332,629      cycles                    #    3.425 
   164,013,912,324      instructions              #    3.99  insns per cycle        
    41,002,424,948      branches                  # 3412.968 M/sec                  

      12.013355313 seconds time elapsed

内核中发生的这项神秘工作是怎么回事?


1 这里我或多或少地互换使用术语 "time" 和 "cycles"。在这些测试中,CPU 运行 变得平稳,因此对一些与涡轮增压相关的热效应取模,周期与时间成正比。

TL;DR

答案很简单。 您的计数器设置为在用户OS中计数Linux 的时间片调度器会定期干扰您的测量,该调度器每秒滴答几次.

幸运的是,在 5 天前调查与@PeterCordes 无关的问题时,我 published a cleaned-up version of my own performance counter access software ,libpfc

libpfc

libpfc 是一个非常低级的库和 Linux 可加载内核模块,我仅使用完整的 Intel Software Developers' Manual 作为参考编写了自己的代码。性能计数工具记录在 SDM 第 3 卷第 18-19 章中。它是通过将特定值写入某些 x86 处理器中存在的特定 MSR(特定于模型的寄存器)来配置的。

可以在我的 Intel Haswell 处理器上配置两种类型的计数器:

  • 固定功能计数器。它们仅限于计算特定类型的事件。可以启用或禁用它们,但无法更改它们跟踪的内容。在 2.4GHz Haswell i7-4700MQ 上有 3 个:

    • 已发布的说明:罐头上的说明。
    • Unhalted Core Cycles:实际发生的时钟滴答数。如果处理器的频率按比例增加或减少,该计数器将在每单位时间内分别更快或更慢地开始计数。
    • Unhalted Reference Cycles:时钟滴答的数量,以不受动态频率缩放影响的恒定频率滴答。在我的 2.4GHz 处理器上,它的时钟频率正好是 2.4GHz。因此,Unhalted Reference / 2.4e9 给出了亚纳秒精度的计时,Unhalted Core / Unhalted Reference 给出了您从 Turbo Boost 获得的平均加速因子。
  • 通用计数器。这些通常可以配置为跟踪在您的特定处理器的 SDM 中列出的任何事件(只有一些限制)。对于 Haswell,它们目前列在 SDM 的第 3 卷,§19.4 中,我的存储库包含一个演示,pfcdemo, that accesses a large subset of them. They're listed at pfcdemo.c:169。在我的 Haswell 处理器上,当启用超线程时,每个内核都有 4 个这样的计数器。

正确配置计数器

为了配置计数器,我承担了自己在我的 LKM 中对每个 MSR 进行编程的负担,pfc.kowhose source code is included in my repository

必须对 MSR 进行编程非常小心,否则处理器会用内核恐慌来惩罚您。出于这个原因,除了通用和固定功能计数器本身之外,我还熟悉了 5 种不同类型 MSR 的 每个位 。我对这些寄存器的注释是 at pfckmod.c:750,转载于此:

/** 186+x IA32_PERFEVTSELx           -  Performance Event Selection, ArchPerfMon v3
 * 
 *                     /63/60 /56     /48     /40     /32     /24     /16     /08     /00
 *                    {................................################################}
 *                                                     |      ||||||||||      ||      |
 *     Counter Mask -----------------------------------^^^^^^^^|||||||||      ||      |
 *     Invert Counter Mask ------------------------------------^||||||||      ||      |
 *     Enable Counter ------------------------------------------^|||||||      ||      |
 *     AnyThread ------------------------------------------------^||||||      ||      |
 *     APIC Interrupt Enable -------------------------------------^|||||      ||      |
 *     Pin Control ------------------------------------------------^||||      ||      |
 *     Edge Detect -------------------------------------------------^|||      ||      |
 *     Operating System Mode ----------------------------------------^||      ||      |
 *     User Mode -----------------------------------------------------^|      ||      |
 *     Unit Mask (UMASK) ----------------------------------------------^^^^^^^^|      |
 *     Event Select -----------------------------------------------------------^^^^^^^^
 */
/** 309+x IA32_FIXED_CTRx            -  Fixed-Function Counter,      ArchPerfMon v3
 * 
 *                     /63/60 /56     /48     /40     /32     /24     /16     /08     /00
 *                    {????????????????????????????????????????????????????????????????}
 *                     |                                                              |
 *     Counter Value --^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 *     
 *     NB: Number of FF counters determined by    CPUID.0x0A.EDX[ 4: 0]
 *     NB: ???? FF counter bitwidth determined by CPUID.0x0A.EDX[12: 5]
 */
/** 38D   IA32_FIXED_CTR_CTRL        -  Fixed Counter Controls,      ArchPerfMon v3
 * 
 *                     /63/60 /56     /48     /40     /32     /24     /16     /08     /00
 *                    {....................................................############}
 *                                                                         |  ||  |||||
 *     IA32_FIXED_CTR2 controls  ------------------------------------------^^^^|  |||||
 *     IA32_FIXED_CTR1 controls  ----------------------------------------------^^^^||||
 *                                                                                 ||||
 *     IA32_FIXED_CTR0 controls:                                                   ||||
 *     IA32_FIXED_CTR0 PMI --------------------------------------------------------^|||
 *     IA32_FIXED_CTR0 AnyThread ---------------------------------------------------^||
 *     IA32_FIXED_CTR0 enable (0:Disable 1:OS 2:User 3:All) -------------------------^^
 */
/** 38E   IA32_PERF_GLOBAL_STATUS    -  Global Overflow Status,      ArchPerfMon v3
 * 
 *                  /63/60 /56     /48     /40     /32     /24     /16     /08     /00
 *                 {###..........................###............................####}
 *                  |||                          |||                            ||||
 *     CondChgd ----^||                          |||                            ||||
 *     OvfDSBuffer --^|                          |||                            ||||
 *     OvfUncore -----^                          |||                            ||||
 *     IA32_FIXED_CTR2 Overflow -----------------^||                            ||||
 *     IA32_FIXED_CTR1 Overflow ------------------^|                            ||||
 *     IA32_FIXED_CTR0 Overflow -------------------^                            ||||
 *     IA32_PMC(N-1)   Overflow ------------------------------------------------^|||
 *     ....                     -------------------------------------------------^||
 *     IA32_PMC1       Overflow --------------------------------------------------^|
 *     IA32_PMC0       Overflow ---------------------------------------------------^
 */
/** 38F   IA32_PERF_GLOBAL_CTRL      -  Global Enable Controls,      ArchPerfMon v3
 * 
 *                     /63/60 /56     /48     /40     /32     /24     /16     /08     /00
 *                    {.............................###............................####}
 *                                                  |||                            ||||
 *     IA32_FIXED_CTR2 enable ----------------------^||                            ||||
 *     IA32_FIXED_CTR1 enable -----------------------^|                            ||||
 *     IA32_FIXED_CTR0 enable ------------------------^                            ||||
 *     IA32_PMC(N-1)   enable -----------------------------------------------------^|||
 *     ....                   ------------------------------------------------------^||
 *     IA32_PMC1       enable -------------------------------------------------------^|
 *     IA32_PMC0       enable --------------------------------------------------------^
 */
/** 390   IA32_PERF_GLOBAL_OVF_CTRL  -  Global Overflow Control,     ArchPerfMon v3
 * 
 *                     /63/60 /56     /48     /40     /32     /24     /16     /08     /00
 *                    {###..........................###............................####}
 *                     |||                          |||                            ||||
 *     ClrCondChgd ----^||                          |||                            ||||
 *     ClrOvfDSBuffer --^|                          |||                            ||||
 *     ClrOvfUncore -----^                          |||                            ||||
 *     IA32_FIXED_CTR2 ClrOverflow -----------------^||                            ||||
 *     IA32_FIXED_CTR1 ClrOverflow ------------------^|                            ||||
 *     IA32_FIXED_CTR0 ClrOverflow -------------------^                            ||||
 *     IA32_PMC(N-1)   ClrOverflow ------------------------------------------------^|||
 *     ....                        -------------------------------------------------^||
 *     IA32_PMC1       ClrOverflow --------------------------------------------------^|
 *     IA32_PMC0       ClrOverflow ---------------------------------------------------^
 */
/** 4C1+x IA32_A_PMCx                -  General-Purpose Counter,     ArchPerfMon v3
 * 
 *                     /63/60 /56     /48     /40     /32     /24     /16     /08     /00
 *                    {????????????????????????????????????????????????????????????????}
 *                     |                                                              |
 *     Counter Value --^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 *     
 *     NB: Number of GP counters determined by    CPUID.0x0A.EAX[15: 8]
 *     NB: ???? GP counter bitwidth determined by CPUID.0x0A.EAX[23:16]
 */

尤其要注意 IA32_PERFEVTSELx,位 16(用户模式)和 17(OS 模式)和 IA32_FIXED_CTR_CTRL,位 IA32_FIXED_CTRx enableIA32_PERFEVTSELx配置通用计数器x,而IA32_FIXED_CTR_CTRL中从位4*x开始的每组4位配置固定功能计数器x .

在MSR IA32_PERFEVTSELx中,如果在设置用户位时清除OS位,计数器将仅在用户模式下累积事件,并且不包括内核模式事件。在 MSR IA32_FIXED_CTRL_CTRL 中,每组 4 位包含一个两位 enable 字段,如果设置为 2 (0b10) 将启用用户模式下的事件计数,但不在内核模式下。

我的 LKM 分别在 pfckmod.c:296 and pfckmod.c:330 对固定功能和通用计数器强制执行仅用户模式计数。

用户-Space

在user-space中,用户配置计数器(过程示例,从pfcdemo.c:98开始),然后使用宏PFCSTART()PFCEND()。这些是非常具体的代码序列,但它们有成本,因此会产生一个有偏差的结果,该结果会高估来自计时器的事件数量。因此,您还必须调用 pfcRemoveBias(),当它们围绕 0 条指令时,它乘以 PFCSTART()/PFCEND(),并从累积计数中消除偏差。

你的代码,在 libpfc 下

我把你的代码放到 pfcdemo.c:130 中,现在它变成了

/************** Hot section **************/
PFCSTART(CNT);
asm volatile(
".intel_syntax noprefix\n\t"
"mov             rax, 1000000000\n\t"
".loop:\n\t"
"nop\n\t"
"dec             rax\n\t"
"nop\n\t"
"jne             .loop\n\t"
".att_syntax noprefix\n\t"
: /* No outputs we care about */
: /* No inputs we care about */
: "rax", "memory", "cc"
);
PFCEND  (CNT);
/************ End Hot section ************/

。我得到以下信息:

Instructions Issued                  :           4000000086
Unhalted core cycles                 :           1001668898
Unhalted reference cycles            :            735432000
uops_issued.any                      :           4000261487
uops_issued.any<1                    :              2445188
uops_issued.any>=1                   :           1000095148
uops_issued.any>=2                   :           1000070454
Instructions Issued                  :           4000000084
Unhalted core cycles                 :           1002792358
Unhalted reference cycles            :            741096720
uops_issued.any>=3                   :           1000057533
uops_issued.any>=4                   :           1000044117
uops_issued.any>=5                   :                    0
uops_issued.any>=6                   :                    0
Instructions Issued                  :           4000000082
Unhalted core cycles                 :           1011149969
Unhalted reference cycles            :            750048048
uops_executed_port.port_0            :            374577796
uops_executed_port.port_1            :            227762669
uops_executed_port.port_2            :                 1077
uops_executed_port.port_3            :                 2271
Instructions Issued                  :           4000000088
Unhalted core cycles                 :           1006474726
Unhalted reference cycles            :            749845800
uops_executed_port.port_4            :                 3436
uops_executed_port.port_5            :            438401716
uops_executed_port.port_6            :           1000083071
uops_executed_port.port_7            :                 1255
Instructions Issued                  :           4000000082
Unhalted core cycles                 :           1009164617
Unhalted reference cycles            :            756860736
resource_stalls.any                  :                 1365
resource_stalls.rs                   :                    0
resource_stalls.sb                   :                    0
resource_stalls.rob                  :                    0
Instructions Issued                  :           4000000083
Unhalted core cycles                 :           1007578976
Unhalted reference cycles            :            755945832
uops_retired.all                     :           4000097703
uops_retired.all<1                   :              8131817
uops_retired.all>=1                  :           1000053694
uops_retired.all>=2                  :           1000023800
Instructions Issued                  :           4000000088
Unhalted core cycles                 :           1015267723
Unhalted reference cycles            :            756582984
uops_retired.all>=3                  :           1000021575
uops_retired.all>=4                  :           1000011412
uops_retired.all>=5                  :                 1452
uops_retired.all>=6                  :                    0
Instructions Issued                  :           4000000086
Unhalted core cycles                 :           1013085918
Unhalted reference cycles            :            758116368
inst_retired.any_p                   :           4000000086
inst_retired.any_p<1                 :             13696825
inst_retired.any_p>=1                :           1000002589
inst_retired.any_p>=2                :           1000000132
Instructions Issued                  :           4000000083
Unhalted core cycles                 :           1004281248
Unhalted reference cycles            :            745031496
inst_retired.any_p>=3                :            999997926
inst_retired.any_p>=4                :            999997925
inst_retired.any_p>=5                :                    0
inst_retired.any_p>=6                :                    0
Instructions Issued                  :           4000000086
Unhalted core cycles                 :           1018752394
Unhalted reference cycles            :            764101152
idq_uops_not_delivered.core          :             71912269
idq_uops_not_delivered.core<1        :           1001512943
idq_uops_not_delivered.core>=1       :             17989688
idq_uops_not_delivered.core>=2       :             17982564
Instructions Issued                  :           4000000081
Unhalted core cycles                 :           1007166725
Unhalted reference cycles            :            755495952
idq_uops_not_delivered.core>=3       :              6848823
idq_uops_not_delivered.core>=4       :              6844506
rs_events.empty                      :                    0
idq.empty                            :              6940084
Instructions Issued                  :           4000000088
Unhalted core cycles                 :           1012633828
Unhalted reference cycles            :            758772576
idq.mite_uops                        :             87578573
idq.dsb_uops                         :                56640
idq.ms_dsb_uops                      :                    0
idq.ms_mite_uops                     :               168161
Instructions Issued                  :           4000000088
Unhalted core cycles                 :           1013799250
Unhalted reference cycles            :            758772144
idq.mite_all_uops                    :            101773478
idq.mite_all_uops<1                  :            988984583
idq.mite_all_uops>=1                 :             25470706
idq.mite_all_uops>=2                 :             25443691
Instructions Issued                  :           4000000087
Unhalted core cycles                 :           1009164246
Unhalted reference cycles            :            758774400
idq.mite_all_uops>=3                 :             16246335
idq.mite_all_uops>=4                 :             16239687
move_elimination.int_not_eliminated  :                    0
move_elimination.simd_not_eliminated :                    0
Instructions Issued                  :           4000000089
Unhalted core cycles                 :           1018530294
Unhalted reference cycles            :            763961712
lsd.uops                             :           3863703268
lsd.uops<1                           :             53262230
lsd.uops>=1                          :            965925817
lsd.uops>=2                          :            965925817
Instructions Issued                  :           4000000082
Unhalted core cycles                 :           1012124380
Unhalted reference cycles            :            759399384
lsd.uops>=3                          :            978583021
lsd.uops>=4                          :            978583021
ild_stall.lcp                        :                    0
ild_stall.iq_full                    :                  863
Instructions Issued                  :           4000000087
Unhalted core cycles                 :           1008976349
Unhalted reference cycles            :            758008488
br_inst_exec.all_branches            :           1000009401
br_inst_exec.0x81                    :           1000009400
br_inst_exec.0x82                    :                    0
icache.misses                        :                  168
Instructions Issued                  :           4000000084
Unhalted core cycles                 :           1010302763
Unhalted reference cycles            :            758333856
br_misp_exec.all_branches            :                    2
br_misp_exec.0x81                    :                    1
br_misp_exec.0x82                    :                    0
fp_assist.any                        :                    0
Instructions Issued                  :           4000000082
Unhalted core cycles                 :           1008514841
Unhalted reference cycles            :            757761792
cpu_clk_unhalted.core_clk            :           1008510494
cpu_clk_unhalted.ref_xclk            :             31573233
baclears.any                         :                    0
idq.ms_uops                          :               164093

没有开销了!你可以看到,从固定功能计数器(例如最后一组打印输出)可以看出 IP​​C 是 4000000082 / 1008514841,大约是 4 个 IPC,从 757761792 / 2.4e9 可以看出代码耗时 0.31573408 秒,从 1008514841 / 757761792 = 1.330912763941521 内核睿频加速到 2.4GHz 的 133%,即 3.2GHz。