Linux 性能启动开销:为什么执行MOV+;SYS_exit有这么多暂停循环(和指令)?

Linux 性能启动开销:为什么执行MOV+;SYS_exit有这么多暂停循环(和指令)?,linux,performance,assembly,x86-64,perf,Linux,Performance,Assembly,X86 64,Perf,我试图理解如何衡量性能,并决定编写一个非常简单的程序: section .text global _start _start: mov rax, 60 syscall 我用perf stat./bin运行了这个程序,令我惊讶的是停止循环的前端太高了 0.038132 task-clock (msec) # 0.148 CPUs utilized 0 context-sw

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

section .text
    global _start

_start:
    mov rax, 60
    syscall
我用
perf stat./bin运行了这个程序,令我惊讶的是
停止循环的前端
太高了

      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
据我所知,暂停周期前端意味着CPU前端必须等待某些操作(如总线事务)的结果完成

那么,在这种最简单的情况下,是什么导致CPU前端等待了大部分时间呢


和2页错误?为什么?我没有读取内存页。

页面错误包括代码页

perf stat
包括启动开销

IDK了解
perf
如何开始计数的详细信息,但它可能必须在内核模式下对性能计数器进行编程,因此它们在CPU切换回用户模式时计数(暂停许多周期,特别是在具有使TLB失效的熔毁防御的内核上)

我想大部分记录的
47654
指令都是内核代码。
可能包括页面错误处理程序

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


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

你怎么不担心47654指令呢?:)不确定perf具体计算的是什么,但很可能它执行的其他任何东西(内核代码?)也会导致暂停。如果您复制/粘贴真实代码(和构建指令),它本来就不可能存在。始终这样做,而不是重新键入代码。如果您正在编写代码,并且没有在任何地方对其进行过测试,则只能在中键入代码。@PeterCordes正是缺少的
\u start:
标签使我认为正常初始化已执行。另外,对于libc链接是如何被抑制的问题也缺乏描述:如果您没有明确尝试这样做,那么要生成没有这些内容的可执行文件是非常困难的。无论如何,不管我的猜测是否正确,我都不会删除我的评论:链接非常好,其他读者可能也感兴趣。@cmaster:
ld foo.o-o foo
在Linux上生成一个静态可执行文件。不,动态链接的原因不是对内核头的依赖(用户空间ABI是稳定的,您不需要不同的glibc来匹配您的内核)。原因是libc没有嵌入到每个二进制文件中,glibc错误修复也不需要重新编译所有文件。加上通常的记忆优势。使用gcc,要使用自定义的
\u start
创建静态可执行文件,请使用
gcc foo.o-nostlib-static
。或者将libc动态链接到您自己的
\u start
gcc foo.o-nostartfiles
(glibc init funcs仍在运行)@ CMASTER:GLUBC init函数运行在动态加载的C++库中运行构造函数的相同机制:<代码> LIBC。因此,6 < /COD>在“代码”>“全局代码”//COD>数组或类似的东西中有必要的init函数的地址,动态链接器在跳到如果gcc告诉链接器,则链接器仅“将代码插入…调用
main
”(即CRT(C运行时)启动文件)。如果运行
gcc-vfoo.c
,您将看到编译器、汇编程序和链接器命令行(
ld
通过
collect2
包装器调用,但您可以看到
gcc
显式传递的
crt0.o
),这是一个快速的部分答案;我希望看到一个答案,它能够准确地解释哪些指令(大概是内核指令)包含在
perf stat
计算为启动/关闭开销的部分中。如果你能控制它;我认为x86性能计数器可以编程为仅在用户模式或内核+用户模式下计数。它统计了10519个事件。最上面是下面一行
35.55%perf[kernel.kallsyms]
。这是否意味着在这种简单的情况下,性能计数TLB未命中开销非常重要?@St.Antario:显然是的。但你有47k指令的10k TLB失误?这听起来高得难以置信。听起来我们需要一个真正的答案来回答这个问题,而不仅仅是我的手在
perf
上挥舞,包括一些启动开销。但是如果
perf
启动开销太高。。。我们如何使用它编写微/纳米基准测试?甚至可能吗?@St.Antario:将代码放入一个至少运行50毫秒的循环中。我有一个
testloop.asm
和一个assembly+link+
perf stat
命令行,我可以很容易地找到它,它使用
mov ebp,100000000
作为
dec ebp/jnz
循环的循环计数器。如果循环中的代码超过几个快速UOP,我可以根据需要添加或删除零。使用
perf stat-r4
,我获得了非常好的重复性和准确性,在0.1到1秒左右的运行时间内,预期结果在10k或100k的1部分之内。看见