Perf启动开销:为什么执行MOV + SYS_exit的简单静态可执行文件有如此多的停顿周期(和指令)?

St.*_*rio 11 linux performance assembly x86-64 perf

我试图了解如何衡量性能并决定编写非常简单的程序:

section .text
    global _start

_start:
    mov rax, 60
    syscall
Run Code Online (Sandbox Code Playgroud)

我用perf stat ./bin 这个程序运行我感到惊讶的stalled-cycles-frontend是太高了.

      0.038132      task-clock (msec)         #    0.148 CPUs utilized          
             0      context-switches          #    0.000 K/sec                  
             0      cpu-migrations            #    0.000 K/sec                  
             2      page-faults               #    0.052 M/sec                  
       107,386      cycles                    #    2.816 GHz                    
        81,229      stalled-cycles-frontend   #   75.64% frontend cycles idle   
        47,654      instructions              #    0.44  insn per cycle         
                                              #    1.70  stalled cycles per insn
         8,601      branches                  #  225.559 M/sec                  
           929      branch-misses             #   10.80% of all branches        

   0.000256994 seconds time elapsed
Run Code Online (Sandbox Code Playgroud)

据我所知,stalled-cycles-frontend这意味着CPU前端必须等待某些操作(例如总线事务)的结果才能完成.

那么是什么导致CPU前端在最简单的情况下大部分时间都在等待?

和2页错误?为什么?我没有读内存页面.

Pet*_*des 2

页面错误包括代码页。

perf stat包括启动开销。

我不知道如何开始计数的细节perf,但大概它必须在内核模式下对性能计数器进行编程,因此它们在 CPU 切换回用户模式进行计数(停滞许多周期,特别是在具有 Meltdown 防御的内核上,这会使TLB)。

我猜大部分47,654记录的指令都是内核代码。 也许包括页面错误处理程序!

我猜你的进程永远不会进入用户->内核->用户,整个过程是内核->用户->内核(启动,syscall调用sys_exit,然后永远不会返回到用户空间),所以永远不会出现TLB会具有的情况无论如何都很热,除了系统调用后在内核中运行时sys_exit。无论如何,TLB 未命中不是页面错误,但这可以解释许多停滞的周期。

顺便说一句,用户->内核转换本身解释了大约 150 个停滞周期。 syscall比缓存未命中更快(除了它不是流水线的,并且实际上刷新整个流水线;即特权级别没有重命名。)


用于perf stat --all-user仅在用户空间中计数。

或者对四个计数器进行编程,其中两个仅在用户模式下对和事件perf stat -e task-clock,cycles:u,instructions:u,cycles:k,instructions:k进行计数,两个仅在内核模式下对事件进行计数。(PMU 中有对此的硬件支持,因此用户空间的计数非常准确,只需要一两条指令。)instructionscycles

也可以看看