最近我在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汇编程序之间的开销变化如此之大?
答案 0 :(得分:2)
声称它“确实没有任何意义”是不公平的。是的,在应用程序级别,您选择使整个事物成为您的微基准测试的巨型无操作,这很好。但是,不,在系统层面的封面下,它几乎没有“没有”。您要求linux分拆一个全新的执行环境,初始化它并将其连接到环境。你调用很少的glibc函数,但动态链接是非常重要的,经过一百万条指令后,你的进程已经准备好要求故障printf()和朋友,并有效地引入你可能已经链接的库或者dlopen()'ed。
这不是实现者可能优化的那种微基础。 会对感兴趣的是,如果你能识别出fork / exec的“昂贵”方面,那么在某些用例中从未使用过,因此可能会#ifdef out(或执行它们的短路) )在非常具体的情况下。对resolv.conf的惰性评估就是其中的一个例子,如果进程从不与IP服务器进行交互,那么进程永远不会支付开销。