为什么 运行 一个空程序需要这么多指令?
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 服务器交互,则进程永远不会支付开销。
所以最近学习了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 服务器交互,则进程永远不会支付开销。