为什么运行一个空程序需要这么多指令?

J. *_*rez 5 linux cpu assembly perf

所以最近学习了perflinux中的命令。我决定进行一些实验,因此我创建了一个空的 C 程序并测量了运行所需的指令数:

echo 'int main(){}'>emptyprogram.c && gcc -O3 emptyprogram.c -o empty
perf stat ./empty
Run Code Online (Sandbox Code Playgroud)

这是输出:

 Performance counter stats for './empty':

      0.341833      task-clock (msec)         #    0.678 CPUs utilized          
             0      context-switches          #    0.000 K/sec                  
             0      cpu-migrations            #    0.000 K/sec                  
           112      page-faults               #    0.328 M/sec                  
     1,187,561      cycles                    #    3.474 GHz                    
     1,550,924      instructions              #    1.31  insn per cycle         
       293,281      branches                  #  857.966 M/sec                  
         4,942      branch-misses             #    1.69% of all branches        

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

为什么它使用这么多指令来运行一个实际上什么也不做的程序?我认为这可能是将程序加载到操作系统中所需的一些基准指令数,因此我寻找了用汇编语言编写的最小可执行文件,并且发现了一个在此处输出的 142 字节可执行文件"Hi World"http://timelessname.txt)。 com/elfbin/

在 142 字节 hello 可执行文件上运行 perf stat,我得到:

Hi World

 Performance counter stats for './hello':

      0.069185      task-clock (msec)         #    0.203 CPUs utilized          
             0      context-switches          #    0.000 K/sec                  
             0      cpu-migrations            #    0.000 K/sec                  
             3      page-faults               #    0.043 M/sec                  
       126,942      cycles                    #    1.835 GHz                    
       116,492      instructions              #    0.92  insn per cycle         
        15,585      branches                  #  225.266 M/sec                  
         1,008      branch-misses             #    6.47% of all branches        

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

这似乎仍然比我的预期高很多,但我们可以接受它作为基线。既然如此,为什么运行empty要多花 10 倍的指令呢?这些指令做了什么?如果它们是某种开销,为什么 C 程序和 helloworld 汇编程序之间的开销差异如此之大?

J_H*_*J_H 2

声称它“实际上什么也没做”是不公平的。是的,在应用程序级别,您选择让整个事情对您的微基准测试来说是一个巨大的无操作,这很好。但不,在系统层面的幕后,它几乎不是“什么都没有”。您要求 linux 分叉一个全新的执行环境,对其进行初始化,并将其连接到该环境。您调用了很少的 glibc 函数,但动态链接并非易事,在一百万条指令之后,您的进程已准备好要求错误 printf() 和朋友,并有效地引入您可能链接或 dlopen() 的库。

这不是实施者可能优化的那种微基准。有趣是,如果您可以识别 fork/exec 的“昂贵”方面,这些方面在某些用例中从未使用过,因此在非常特定的情况下可能会被 #ifdef 淘汰(或使其执行短路)。resolv.conf 的惰性评估就是其中一个例子,如果进程从不与 IP 服务器交互,则它永远不会支付开销。