博客首发于:www.weeco.tech/9a99f71cc73…
在之前的实验中,我们发现,C语言代码编译出的二进制文件,其执行效率远高于Go语言编译出的二进制文件,其文件大小也小于Go语言编译生成的。
在编译C语言代码的时候,执行的指令是
gcc is_prime.c -O3 -o is_prime -lm
怀疑与编译指令中的O3有关,所有本篇博客我们就对这个参数进行一个探究。
实验环境介绍
虚拟机:host-CPU直通 Linux CentOS 8.2
CPU:Intel(R) Core(TM) i7-10700K CPU @ 3.80GHz
gcc版本:8.5.0-4
g++版本:8.5.0-4
测试所用代码
#include <stdio.h>
#include <stdbool.h>
#include <math.h>
bool is_prime(int n) {
if (n < 2) {
return false;
}
for (int i = 2; i <= sqrt(n); i++) {
if (n % i == 0) {
return false;
}
}
return true;
}
int main() {
for (int i = 2; i < 10000000; i++) {
if (is_prime(i)) {
printf("%d\n", i);
}
}
return 0;
}
实验设计
分别用如下指令进行程序编译,得到5个不同的二进制文件
gcc is_prime.c -O3 -o is_prime_O3 -lm
gcc is_prime.c -O2 -o is_prime_O2 -lm
gcc is_prime.c -O1 -o is_prime_O1 -lm
gcc is_prime.c -O0 -o is_prime_O0 -lm
gcc is_prime.c -o is_prime -lm
分别统计每个程序的执行时间,可以看出,-O3
的性能远高于-O0
/bin/time -vvv ./is_prime_O3 > /dev/null => 3.19 s
/bin/time -vvv ./is_prime_O2 > /dev/null => 3.61 s
/bin/time -vvv ./is_prime_O1 > /dev/null => 4.02 s
/bin/time -vvv ./is_prime_O0 > /dev/null => 8.73 s
/bin/time -vvv ./is_prime > /dev/null => 8.75 s
通过md5sum 查看生成的二进制可执行文件,发现is_prime文件和is_prime_O0是一致的,即gcc默认采用了-O0优化
我们直接对比is_prime_O0
和is_prime_O3
这两个程序的性能差异,通过objdump -S
指令查看两者汇编代码层的差异
利用BCompare工具,截取了其中一段差异较大的汇编代码,左图为-O3
程序的汇编指令,右图为-O0
程序的汇编指令
对比发现,-O3
程序采用了一些更高级的汇编指令。
进一步探究,猜测两者的性能差异主要来自于sqrtsd
这个汇编指令。因为整个运算中最消耗计算资源的是开方操作。sqrtsd
是x86架构的一条汇编指令,用于计算一个双精度浮点数的平方根。为了验证这个想法,我将代码进行了简单改造,将sqrt(n)
替换为了>>1
,即右移一位。
#include <stdio.h>
#include <stdbool.h>
#include <math.h>
bool is_prime(int n) {
if (n < 2) {
return false;
}
for (int i = 2; i <= n >> 1; i++) {
if (n % i == 0) {
return false;
}
}
return true;
}
int main() {
for (int i = 2; i < 1000000; i++) {
if (is_prime(i)) {
printf("%d\n", i);
}
}
return 0;
}
测试了一下gcc -O3和gcc -O0的运行时间差异,惊奇地发现,-O3
和-O0
的执行时间完全一致,均为33.46s,也就是从侧面应证了,在上述实验中,性能差异的主要来自于开方操作。
# /usr/bin/time -vvv ./is_prime > /dev/null
Command being timed: "./is_prime"
User time (seconds): 33.46
System time (seconds): 0.00
Percent of CPU this job got: 99%
Elapsed (wall clock) time (h:mm:ss or m:ss): 0:33.57
Average shared text size (kbytes): 0
Average unshared data size (kbytes): 0
Average stack size (kbytes): 0
Average total size (kbytes): 0
Maximum resident set size (kbytes): 1376
Average resident set size (kbytes): 0
Major (requiring I/O) page faults: 0
Minor (reclaiming a frame) page faults: 57
Voluntary context switches: 1
Involuntary context switches: 85
Swaps: 0
File system inputs: 0
File system outputs: 0
Socket messages sent: 0
Socket messages received: 0
Signals delivered: 0
Page size (bytes): 4096
Exit status: 0
初步结论
在大多数场景下,gcc -O3
比gcc -O0
有更优的性能。
在某些场景下,例如程序相对简单的时候,gcc没有太多优化空间,此时gcc -O3
的效果与gcc -O0
的效果完全一致。
下一步计划
C/C++一般都采用动态编译,就需要依赖动态链接库,为什么不采用静态编译呢?两者有咋样的性能差异呢?下次博客我们来探索这个问题~~