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

Why does it take so many instructions to run an empty program?

所以最近学习了linux中的perf命令。我决定 运行 一些实验,所以我创建了一个空的 C 程序并测量了 运行 需要多少指令:

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

这是输出:

 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

为什么要对 运行 一个几乎什么都不做的程序使用这么多指令?我认为这可能是将程序加载到 OS 所需的一些基线指令数,所以我寻找了一个用汇编语言编写的最小可执行文件,并找到了一个输出 "Hi World" 的 142 字节可执行文件] 这里 (http://timelessname.com/elfbin/)

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

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

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

声称它 "does literally nothing" 是不公平的。是的,在应用程序级别,你选择让整个事情成为你的微基准测试的一个巨大的空操作,这很好。但是不,在系统级别的幕后,它几乎不是 "nothing"。您要求 linux 分叉出一个全新的执行环境,对其进行初始化,并将其连接到该环境。您调用了很少的 glibc 函数,但动态链接非常重要,在一百万条指令之后,您的进程已准备好要求错误 printf() 和朋友,并有效地引入您可能已链接或 dlopen() 的库。

这不是实现者可能针对其进行优化的那种微平台。 感兴趣的是,如果您可以识别 fork/exec 在某些用例中从未使用过的 "expensive" 方面,因此可能会被 #ifdef 淘汰(或让他们的执行短路)在非常特殊的情况下。 resolv.conf 的惰性评估就是其中一个例子,如果进程从不与 IP 服务器交互,则进程永远不会支付开销。