工欲善其事,必先利其器。对于嵌入式工程师而言,嵌入式编译器是不可或缺的核心工具,常被称为“C语言翻译官”。由于C语言历史悠久且早期缺乏统一规范,加上计算机产业早期的拓荒特性,市场上涌现了多种C语言编译器。
根据相关调研,嵌入式工程师较为青睐的编译器主要包括Keil(ArmCC)、IAR、GCC、AVR GCC、CLion、Clang、Green Hills、TI的CSS以及ADI的Visual DSP++等。随着嵌入式开发格局的逐渐稳固,Keil、IAR与GCC已形成三足鼎立之势,覆盖了绝大部分嵌入式产品的开发。尤其是GCC,作为一个完全开源的编译器,它被众多MCU厂商作为基础,改写成自家的集成开发环境(IDE)。然而,近来业界出现了一种声音,认为“开源才是最贵的”,暗示这些开源编译器背后可能隐藏着诸多隐形成本。
RTOS场景下,GCC性能不及IAR? 
当前,“C/C++与RTOS结合使用”已成为嵌入式软件开发的黄金范式。因此,在嵌入式领域评判一个编译器是否“好用”,其在RTOS上的表现是关键指标。
根据知名嵌入式专家Jacob Beningo的测试,在使用RTOS时,IAR编译器编译出的代码性能指标通常显著优于GCC。尽管具体的公制测试结果有所差异,但IAR的优势普遍在20%到40%之间。以下是一组示例结果,该结果以IAR的指标除以ThreadX(Eclipse ThreadX)在GCC下的指标得出:
- RTOS测试:ThreadX
- 基准测试:74%
- 系统调度:3%
- 内存分配测试:28%
- 消息处理:41%
- 抢占式调度:19%
- 同步处理:32%
或许有人会质疑,代码的编写质量才是决定性能的根本。此话不假,开发者确实可以通过投入大量时间对代码进行精细优化来提升性能,但这无疑意味着更长的开发周期和更高的开发成本。
例如,一些工期紧张的物联网(IoT)项目,若未及时进行代码优化,使用GCC可能导致处理时间增加20%,进而加剧电池消耗。又或者,GCC编译出的代码体积可能更大,迫使产品需要从120MHz升级到200MHz的MCU以容纳代码,这区区几块钱的芯片成本差异,却可能导致整体产品成本大幅攀升。
GCC无疑是行业的福音,但相比于商业编译器,它或许本身就伴随着额外的、隐性的开销。目前,许多RTOS,尤其是开源RTOS,通常只附带支持GCC的工具链,而不支持IAR等商业编译器,这在一定程度上限制了开发者的选择范围。
Keil编译出的文件体积,比GCC更小 
尽管Keil提供了包含全免费教育版在内的多个版本,且针对STM32等主流MCU开发是免费的,但在进行某些深度开发时,Keil本身仍属于需要付费的商业编译器范畴。
在工程师社群中,经常能听到关于Keil的ArmCC编译器表现“神奇”的讨论。实际情况究竟如何?
根据一些工程师的实际测试,在开启多线程编译的前提下(对比对象包括Keil和VisualGDB),GCC的编译速度最快。然而,生成的可执行文件(bin)体积最小的则是ArmCC V5(代码执行效率未在此次测试中对比)。进一步对比ArmCC V5和V6,两者编译用时相差不大,可视为误差范围,但V5生成的bin文件体积比V6小得多。
|
优化选项 |
O0 |
O1 |
O2 |
O3 |
Ofast |
Os |
Oz |
| ARMCC V6.9 |
bin大小(KB) |
131.13 |
90.03 |
95.54 |
98.54 |
97.45 |
87.84kB |
85.47 |
|
编译用时(秒) |
7.23 |
7.52 |
7.99 |
8.3 |
8.4 |
8.17 |
7.64 |
| ARMCC V5.06 |
bin大小(KB) |
77.18 |
64.49 |
61.19 |
61.44 |
- - |
- - |
- - |
|
编译用时(秒) |
7.93 |
8.25 |
8.15 |
9.68 |
- - |
- - |
- - |
| GCC 7.2 |
bin大小(KB) |
176 |
135 |
136 |
144 |
- - |
129 |
- - |
|
编译用时(秒) |
3.49 |
3.63 |
3.68 |
4.12 |
- - |
3.96 |
- - |
为何不同编译器产生的文件大小会有如此差异?有工程师曾遇到过GCC编译的bin文件比ArmCC大的情况,通过梳理代码发现,部分芯片原厂提供的底层代码已针对特定编译器做了优化,这实际上为工程师节省了自行优化的时间。
也有观点认为,Keil与GCC各有优势,通常难以兼得:Keil(ArmCC)对Arm芯片有天然优势,在代码执行效率和尺寸上表现更佳;而GCC的优势在于开源,给予开发者更大的定制和探索空间。
更有工程师在Cortex-M0内核上进行了实验,使用相同代码触发PendSV中断,测得ArmCC的响应时间为68个时钟周期,而GCC则为78个时钟周期。该工程师表示,尽管ArmCC不开源、不免费且不可控,但其生成的代码执行效率确实更高。

ArmCC中断响应汇编

GCC中断响应汇编
工程师的选择:各持己见,适者为佳 
正所谓萝卜青菜各有所爱。无论测试数据如何,工程师们总有自己偏好的工具。这往往源于公司项目的不同需求——对Flash大小、执行速度、交付周期的要求各异,导致不同编译器的适用性结果也不同。
- Keil支持者:在实际体验中,AC6版本的“Osize”优化效果非常显著,虽然性能可能略弱一丝,但生成的代码体积特别小,其他编译器难以匹敌,相同程序可比GCC小四分之一。不过Keil也让人“又爱又恨”,例如MDK AC6曾出现调试时光标乱跳的问题(商业版有所改善),或者经常将if-else结构优化成IT汇编指令,导致在反汇编窗口中设置的断点命中却未实际执行。不少用户表示,已经习惯了。
- IAR青睐者:认为IAR不仅比Keil便宜不少,而且内置了MISRA静态检查工具,使用起来非常方便。
- GCC传统派:青睐GCC+GDB调试配合VSCode编辑,在Linux系统下编译体验流畅。在某些对Flash容量不敏感的项目中,这能带来最快的交付速度。
- 新锐工具派:也有工程师转向Clion、Clang等更现代的工具链。
那么,你如何看待不同编译器之间的这些差异?在实际的嵌入式开发项目中,你又会如何做出选择?
参考文献
[1] https://www.embedded.com/the-hidden-costs-of-open-source-compilers
[2] https://www.zhihu.com/question/26864086/answer/280900095
[3] https://club.rt-thread.org/ask/article/866813c0a2efd608.html
[4] https://www.bilibili.com/video/BV18h4y1v7yR
在技术选型的道路上,没有绝对的“最好”,只有最适合当前项目需求的“更好”。希望本文的对比能为你提供有价值的参考。如果你对嵌入式开发或编译器原理有更深入的兴趣,欢迎在云栈社区与更多开发者交流探讨。