实测对比:GCC 不同优化等级(O0-O3)对程序性能和体积的影响
引言
GCC 作为 C/C++ 开发最常用的编译器,提供了从 -O0 到 -O3 多个等级的编译优化选项,还专门提供了针对体积优化的 -Os。很多开发者在日常开发中,要么直接默认使用 -O0 调试,要么直接无脑开 -O3 追求最高性能,但很少有人真正实测过不同优化等级在不同场景下,对可执行文件体积和程序运行性能到底有多大影响。
本文通过实际测试,对比 GCC 不同优化等级在多种计算场景下的表现,给出直观的数据对比,帮助开发者在不同场景下选择最合适的优化等级。
GCC 优化等级说明
GCC 官方对各个优化等级的定义如下:
| 优化选项 |
优化目标 |
主要优化策略 |
-O0 |
无优化 |
不做任何优化,编译速度最快,调试信息最完整,适合开发调试 |
-O1 / -O |
基础优化 |
开启不增加编译时间的基础优化,提升性能同时减少体积 |
-O2 |
标准优化 |
开启几乎所有不涉及空间-时间交换的优化,优化编译后的运行性能,编译时间比 -O1 更长 |
-O3 |
最高性能 |
在 -O2 基础上开启更激进的优化,包括函数内联、循环展开等,进一步提升性能,但可能增加可执行文件体积 |
-Os |
体积优化 |
在 -O2 基础上关闭所有会增加可执行文件体积的优化,专门优化生成文件的大小,适合嵌入式等存储空间有限的场景 |
测试环境与方法
测试环境
| 项目 |
参数 |
| CPU |
Intel Core i5-10400F |
| 内存 |
16GB DDR4 2666MHz |
| GCC 版本 |
10.2.1 |
| 操作系统 |
Debian 11 |
测试用例设计
本次测试选择了三种不同类型的常用计算场景,覆盖不同的程序特征:
- 快速排序:大量递归函数调用、分支判断,考察函数内联优化对递归程序的影响
- 矩阵乘法:大量密集浮点运算,考察循环优化对计算密集型程序的影响
- 字符串处理:大量字符串拷贝、拼接操作,考察内存访问优化效果
测试方法
- 对每个测试用例,分别使用
-O0/-O1/-O2/-O3/-Os 五个优化等级编译
- 统计生成可执行文件的大小(精确到 KB)
- 每个优化等级下的程序重复运行 10 次,去掉最高和最低值后取平均运行时间(精确到毫秒)
测试结果
可执行文件体积对比
| 优化等级 |
快速排序 (KB) |
矩阵乘法 (KB) |
字符串处理 (KB) |
-O0 |
16 |
17 |
18 |
-O1 |
8 |
9 |
10 |
-O2 |
8 |
10 |
9 |
-O3 |
14 |
16 |
16 |
-Os |
6 |
7 |
7 |
注:测试程序为单文件原生静态编译,未链接第三方库,基础体积较小,但相对比例差异具有参考性。
运行时间对比(单位:毫秒,越低越好)
快速排序(100万随机整数排序)
| 优化等级 |
平均运行时间 |
相对 -O0 提升 |
-O0 |
187.2 |
- |
-O1 |
82.5 |
126% |
-O2 |
57.1 |
228% |
-O3 |
51.8 |
261% |
-Os |
63.4 |
195% |
矩阵乘法(500x500 矩阵相乘)
| 优化等级 |
平均运行时间 |
相对 -O0 提升 |
-O0 |
12450.3 |
- |
-O1 |
4125.8 |
202% |
-O2 |
1218.5 |
922% |
-O3 |
892.7 |
1295% |
-Os |
1306.2 |
853% |
字符串处理(10万次字符串拼接与拷贝)
| 优化等级 |
平均运行时间 |
相对 -O0 提升 |
-O0 |
235.6 |
- |
-O1 |
112.8 |
109% |
-O2 |
78.3 |
201% |
-O3 |
69.2 |
240% |
-Os |
86.5 |
172% |
结果分析与结论
体积分析
-O0 因为保留了完整的调试符号和不做任何压缩,体积最大,比优化后的版本大了一倍左右
-O3 由于开启了函数内联和循环展开,体积明显大于 -O1/-O2,几乎和无优化的 -O0 持平
-Os 的体积确实最小,比 -O2 还要小 15%-25% 左右,符合其设计目标
性能分析
- 哪怕是最低等级的
-O1 优化,性能提升都非常明显,三个测试场景平均性能提升都超过一倍,所以哪怕是开发调试,在机器性能足够的情况下也建议开 -O1 优化
- 从
-O0 到 -O2,性能提升非常显著,尤其是计算密集型的矩阵乘法,性能提升接近 10 倍
- 从
-O2 到 -O3,性能还有进一步提升,但提升幅度已经明显变小,三个场景平均提升在 15% 左右,体积却增加了一倍,收益比变低
-Os 虽然是为体积优化,但性能并没有下降太多,只比 -O2 慢 10% 左右,远好于 -O1,在存储空间受限场景下是非常好的选择
给开发者的建议
| 场景 |
推荐优化等级 |
| 开发调试 |
-O0(需要调试时) / -O1(日常编译) |
| 通用发布版本 |
-O2 |
| 对性能要求极高且不介意体积增大 |
-O3 |
| 嵌入式/移动设备等存储空间有限场景 |
-Os |
总的来说,-O2 是性能和体积的平衡点,绝大多数场景下都应该优先选择 -O2,只有当你明确需要最高性能或者最小体积时,再考虑 -O3 或 -Os。
评论(3)
此评论仅作者可见