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

时间:2017-12-09 01:13:20

标签: linux assembly cpu perf

最近我在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

为什么它使用如此多的指令来运行一个几乎没有任何东西的程序?我想也许这是将程序加载到操作系统所需的一些基线指令,所以我找了一个用汇编编写的最小可执行文件,我找到了一个142字节的可执行文件,在这里输出"Hi World"({ {3}})

在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

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

1 个答案:

答案 0 :(得分:2)

声称它“确实没有任何意义”是不公平的。是的,在应用程序级别,您选择使整个事物成为您的微基准测试的巨型无操作,这很好。但是,不,在系统层面的封面下,它几乎没有“没有”。您要求linux分拆一个全新的执行环境,初始化它并将其连接到环境。你调用很少的glibc函数,但动态链接是非常重要的,经过一百万条指令后,你的进程已经准备好要求故障printf()和朋友,并有效地引入你可能已经链接的库或者dlopen()'ed。

这不是实现者可能优化的那种微基础。 会对感兴趣的是,如果你能识别出fork / exec的“昂贵”方面,那么在某些用例中从未使用过,因此可能会#ifdef out(或执行它们的短路) )在非常具体的情况下。对resolv.conf的惰性评估就是其中的一个例子,如果进程从不与IP服务器进行交互,那么进程永远不会支付开销。