【问题标题】:Why does it take so many instructions to run an empty program?为什么运行一个空程序需要这么多指令?
【发布时间】:2018-05-23 06:54:41
【问题描述】:

所以最近我了解了 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" (http://timelessname.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

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

【问题讨论】:

  • 您的“空”程序确实会动态加载共享库(至少我猜是 clib - 我猜您有 64b linux,默认情况下二进制文件是 PIE 并使用共享库),然后初始化C-runtime 环境,然后它最终将控制权交给您的空main(...),它刚刚返回。然后它必须从 C 运行时释放 + 清理所有内容,刷新缓冲区等......
  • 可以添加gcc -O3 -static emptyprogram.c -o empty1; perf stat ./empty1的结果吗?
  • 所以每次运行程序时使用 C 代替汇编会浪费 0.00016 秒?
  • 之前有几个关于perf 指令计数的问题。 This one 建议用户与内核+用户计数可能解释了裸静态可执行文件的高计数。您是否尝试过一个静态可执行文件,它也无需调用 sys_write 就退出?
  • 当我查看用 C 编写的空程序时,我发现动态链接器 ldd.so 占据了大部分指令,正如 @Ped7g 所暗示的那样。您可以尝试静态链接以查看差异,同时保留 C-rutnime init。

标签: linux assembly cpu perf


【解决方案1】:

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

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

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-12-22
  • 2011-04-25
  • 1970-01-01
  • 2018-01-28
  • 2014-10-07
  • 1970-01-01
  • 2017-09-22
相关资源
最近更新 更多