在嵌入式开发中,代码的体积和运行效率非常重要,代码体积往往和芯片的FLASH、RAM容量对应,程序的运行效率也要求在相应能力的处理器上运行。在大多数情况下,成熟的开发人员都希望降低代码体积、提高代码运行效率,然而具体该怎么做呢?

在嵌入式开发中,代码的体积和运行效率非常重要,代码体积往往和芯片的FLASH、RAM容量对应,程序的运行效率也要求在相应能力的处理器上运行。在大多数情况下,成熟的开发人员都希望降低代码体积、提高代码运行效率,然而具体该怎么做呢?本篇文章将以编译器厂商IAR Systems的编译器为例,来解答开发人员在实际工作中常常遇到的问题,工程师朋友们可以在IAR编译器上进行实践验证。

对于嵌入式系统,最终代码的体积和效率取决于由编译器生成的可执行代码,而非开发人员编写的源代码;但是源代码的优化,可以帮助编译器生成更加优质的可执行代码。因此,开发人员不仅要从整体效率等因素上去构思源代码体系,也要高度关注编译器的性能和编译优化的便捷性。

有优化功能的编译器可生成既小又快的可执行代码,编译器是通过对源代码的重复转换来实现优化。通常,编译器优化会遵循完善的数学或逻辑理论基础。但是某些编译优化则是通过启发式的方法,经验表明,一些代码转换往往会产生更好的代码,或者开拓出进一步编译优化的空间。

编译优化只有少数情况依赖于编译器的黑科技,大多数时候编写源代码的方式决定了程序是否可以被编译器优化。在某些情况下,即使对源代码做微小改动也会对编译器生成的代码效率产生重大影响。

本文将讲述在编写代码时需要注意的事项,但我们首先应明确一点,我们没有必要尽量减少代码量,因为即使在一个表达式中使用 ?:- 表达式、后增量和逗号表达式来消除副作用,也不会使编译器产生更有效的代码。这只会使你的源代码变得晦涩难懂,难以维护。例如在一个复杂的表达式中间加入一个后增量或赋值,则在读代码的时候很容易被忽略。请尽量用一种易于阅读的风格来编写代码。

循环

下面看似简单的循环会报错吗?

for (i = 0; i != n; ++i) 

a[i] = b[i]; 

}

虽然不会报错,但其中有几点会影响到编译器生成的代码效率。

例如,索引变量的类型应与指针相匹配。

像 a[i] 这样的数组表达式实际上是 *(&a[0]+i*sizeof(a[0]),或者通俗地说:将第 i个元素的偏移量加到 a 的第一个元素的指针上。对于指针运算, 索引表达式的类型最好与指针所指向的类型一致(__far 指针除外,因为其指针所指向的类型和索引表达式的类型不同)。如果索引表达式的类型与指针所指向的类型不匹配,那么在把它与指针相加之前,必须将它强制转换为正确的类型。

如果在应用中,堆栈空间资源(堆栈一般放在RAM中)比代码尺寸资源(代码一般放在ROM或者Flash中)更宝贵,则可以为索引变量选择一个更小的类型来减少堆栈空间的使用,但这往往会牺牲代码尺寸和执行时间(代码尺寸变大,执行时间变慢)。不仅如此,这种转换也会妨碍循环代码的优化。

除上述问题外,我们也要关注循环条件,因为只有在进入循环之前可以计算出迭代次数的情况下,才可以进行循环优化。然而,这项计算工作非常复杂,并非用最终值减去初始值并除以增量那么简单。例如,如果 i 是一个无符号字符,n 是一个整数,而 n 的值是 1000,那么会发生什么情况?答案是变量 i 在达到 1000 之前就会溢出。

虽然程序员肯定不想要一个无限循环,重复地将 256 个元素从 b 复制到 a,但是编译器无法了解程序员的意图。它必须假设最坏的情况,并且不能应用需要在进入循环之前提供行程数的优化。此外,如果最终值是一个变量,您还应该避免在循环条件中使用关系运算符 <= 和 >=。如果循环条件是 i <= n,那么 n 有可能是该类型中可表示的最高值,因此编译器必须假定这是一个潜在的无限循环。

别名

通常,我们不建议使用全局变量。这是因为您可在程序的任何地方修改全局变量,并且程序会因全局变量的值而变化。这就会形成复杂的依赖关系,使人很难理解程序,也很难确定改变全局变量的值会对程序产生怎样的影响。从优化器的角度来看,这种情况更糟糕,因为通过指针的存储就可以改变任意全局变量的值。如果能通过多种方式访问一个变量,这种情况就会被称为别名,而别名使代码更难优化。

char *buf

void clear_buf() 

{

 int i; 

 for (i = 0; i < 128; ++i) 

 { 

 buf[i] = 0; 

 } 

}

尽管程序员知道向 buf 所指向的缓存区进行写操作不会改变这个buf变量本身,但编译器还是不得不做最坏的打算,在循环的每一次迭代中从内存中重新加载 buf。

如果将缓存区的地址作为参数传递,而不是使用全局变量,则可以消除别名:

void clear_buf(char *buf)

 int i; 

 for (i = 0; i < 128; ++i) 

 { 

 buf[i] = 0;

 } 

}

使用这个解决方案后,指针 buf 就不会被通过指针的存储影响。如此一来,指针 buf 在循环中就可以保持不变,其值只需在循环前加载一次即可,而不是在每次迭代时都要重新加载。

然而,如果需要在不共享调用者/被调用者关系的代码段之间传递信息,则直接使用全局变量即可。但是,对于计算密集型任务,尤其是涉及指针操作时,最好使用自动变量。

尽量不用后增量和后减量

在下文中,关于后增量的所有内容也适用于后减量。C 语言中关于后增量语义的标准文本指出:“后缀 ++ 运算符的结果是操作数的值。在得到结果后,操作数的值会递增”。虽然微控制器普遍拥有可在加载或存储操作后增加指针的寻址模式,但其中只有很少能以同样的效率处理其他类型的后增量。为符合标准,编译器必须在执行增量之前将操作数复制到一个临时变量。对于直线代码来说,可以从表达式中取出增量,然后放在表达式之后。比如以下表达式:

foo = a[i++];

可以改为

foo = a[i];

i = i + 1;

但如果后增量属于 while 循环中的条件,又会发生什么?由于在条件后面没有可以插入增量的地方,因此必须在测试前添加增量。对于这些常见但是又与生成可执行代码效率密切相关的设计,诸如IAR Systems的Embedded Workbench这样的工具都在总结了大量实践后提供了优化方案。

比如以下循环

i = 0;

while (a[i++] != 0)

 {

 ... 

}

应改为

loop: 

 temp = i; /* 保存操作数的值 */

 i = temp + 1; /* 递增操作数 */ 

 if (a[temp] == 0) /* 使用保存的值 */ 

 goto no_loop;

 ... 

 goto loop; 

no_loop:

loop: 

 temp = a[i]; /* 使用操作数的值 */

 i = i + 1; /* 递增操作数 */

 if (temp == 0)

 goto no_loop;

 ... 

 goto loop; 

no_loop:

如果循环后的 i 的值不相关,最好将增量放在循环内。比如以下几乎相同的循环

i = 0; 

while (a[i] != 0) 

++i; 

... 

}

可以在没有临时变量的情况下执行:

loop:

if (a[i] == 0) 

goto no_loop;

 i = i + 1;

 ... 

goto loop; 

no_loop:

优化编译器的开发者们很清楚后增量会使代码编写变得更复杂,尽管我们已尽力去识别这些模式,并尽量消除临时变量,但总有一些情况使我们无法产生有效代码,尤其是遇到比上述更复杂的循环条件时。通常,我们会将一个复杂的表达式分割成若干个更简单的表达式,就像上面的循环条件被分割成一个测试和一个增量那样。

在 C++ 环境中,选择前增量还是后增量的重要性更高。这是因为 operator++ 和 operator-- 都可以以前缀和后缀的形式重载。将运算符作为类对象重载时,虽然没必要模仿基本类型运算符的行为,但也应尽量接近。因此,对于那些可以直观地对对象进行递增和递减的类,例如迭代器,通常会有前缀(operator++() 和 operator--())和后缀形式(operator++(int) 和 operator--(int))。

为了模拟基本类型的前缀 ++ 的行为,operator++() 可以修改对象并返回对修改后对象的引用。那么模拟基本类型的后缀 ++ 的行为会怎样?您还记得吗?“后缀 ++ 运算符的结果是操作数的值。在得到结果后,操作数的值会递增”。就像上面的非直线代码一样,operator++(int) 的实现者必须复制原始对象,修改原始对象,并按值返回副本。由于存在复制操作,因此 operator++(int) 的开销要高于 operator++()。

对于基本类型,如果忽略 i++ 的结果,优化器通常可以消除不必要的复制,但优化器不能将对一个重载运算符的调用变为另一个。如果您出于习惯编写 i++ 而不是 ++i,您就会调用开销更大的增量运算符。

虽然我们一直在反对使用后增量,但不得不承认,后增量在有些情况下还是有用的。如果确实要给一个变量进行后置增量操作,那就继续吧。如果后增量操作和您期望的操作一致,可以使用后增量操作。但请注意,切勿为避免多写一行代码来递增变量,而使用后增量操作。

每当您在循环条件、if 条件、switch 表达式、?:- 表达式或函数调用参数中添加不必要的后增量时,都会使编译器不得不生成更大、更慢的代码。这个清单是不是太长了,记不住?今天就开始培养好的习惯吧!在使用后增量操作前,先问问自己能不能把增量操作作为下一条语句。

结语

当然,软件开发工作并不是只要求开发人员去“将就”编译器,他们与编译器之间的相互协同是快速而高效地完成编程工作的基础之一。此外,从编译器的发展过程来看,它们不仅要跟随技术和语言的演进而迭代和创新,而且还要广泛参考更多的开发习惯,那些历史更悠久、使用更广泛的编译器可以为开发人员带来更高的效率。

因此,在了解了如何编写利于一款优秀编译器优化的代码之后,用户们的工作效率就可以事半功倍。本文中提到的这些原理和tips,也是IAR Systems这样的公司长时间总结的最优实践,而且都可以在该公司的Embedded Workbench中进行验证和探索,在其工具界面中可以查看代码的执行时间和代码尺寸,从而找到最佳解决方案。

好的工具除了通用的代码编译优化,还支持高度灵活的自定义优化设置,如IAR Embedded Workbench包含针对运行效率和代码体积的不同优化等级,对于不同的应用需求,还可以设置从整个工程,到每个源代码文件,甚至是每个函数的优化等级,帮助工程师为自己的应用适配出最佳的优化方案。希望此篇文章对于开发人员更深度地了解程序优化有所帮助。

责编:Luffy Liu

阅读全文,请先
您可能感兴趣
当前,国产EDA发展正面临三大核心挑战:如何加速新工艺的快速导入、如何确保芯片在设计阶段得到充分验证、能否提供更快更精确的制造端软件工具……
通过机器学习技术,EDA工具可以获取更精确的模型来预测设计中存在的问题,如布线拥塞、信号干扰、热效应等,从而为用户提供更准确快速的指导,避免后期返工。
该小组汇集了国内外领先的芯片及IP、嵌入式开发工具、操作系统等厂商,形成强有力的组织,使命是通过一站式功能安全认证服务,帮助企业提升认证价值,满足IEC 61508、ISO 26262等国际功能安全认证标准,从而更高效地达成功能安全要求。
王长明和他的团队开发了一套针对癫痫患者的认知和情绪管理的数字化方案。该方案包括电子化认知功能评估、在线认知训练以及自助式心理治疗三个部分,旨在通过数字化的手段,为患者提供全面、个性化的干预和治疗。
为了应对孤独症治疗的挑战,千丘智能团队开发了一系列基于虚拟现实(VR)和扩展现实(XR)技术的数字疗法产品……
何同学在视频中声称“我们专门写了一个软件”,但随后被网友指出,该软件实际上是GitHub上的开源项目“ASCII generator”,并且何同学在视频中删除了原作者的信息。
• 目前,iPhone在翻新市场中是最热门的商品,并将长期主导着翻新机的平均销售价格。 • 全球翻新机市场持续向高端化发展,其平均销售价格(ASP)现已超过新手机。 • 新兴市场是增长的最大驱动力,消费者对高端旗舰产品有着迫切需求。 • 由于市场固化和供应链的一些问题限制推高中国、东南亚和非洲等大市场的价格。 • 2024年,这些翻新机平均销售价格将首次超过新手机。
从全球厂商竞争来看,三季度凭借多个新品发布,石头科技市场份额提升至16.4%,连续两季度排名全球第一……
2025年1月9日,美国 拉斯维加斯丨全球瞩目的国际消费电子产品展(CES 2025)盛大开幕,来自世界各地的科技巨头与创新企业齐聚一堂共同展示最新的科技成果。中国高性能微控制器产品及嵌入式解决方案提供商上海先楫半导体科技有限公司(先楫半导体,HPMicro)闪耀登场,发布了专注于机器人运动与控制的高性能MCU产品——HPM6E8Y系列,为火热的机器人市场注入新的活力。
最新Wi-Fi HaLow片上系统(SoC)为物联网的性能、效率、安全性与多功能性设立新标准,配套USB网关,可轻松实现Wi-Fi HaLow在新建及现有Wi-Fi基础设施中的快速稳健集成
大疆发布DJI Matrice 4T旗舰无人机,售价38888元。该无人机可用于电力巡检、应急抢险、公共安全、水利林业监测等众多应用场景。DJI Matrice 4T的镜头模组拥有“六个眼”,除了广角
1月8日消息,奥康国际发布公告称,终止发行股份购买资产,公司股票将于1月8日开市起复牌。至此,奥康国际谋划的跨界收购芯片公司事项告一段落。奥康国际在公告中介绍,公司于2024年12月24日披露了《关于
‌‌Jan. 9, 2025 产业洞察根据TrendForce集邦咨询最新研究,随着人型机器人迈向高度系统整合,并有望从工业场景走进家庭生活,前端的AI模型训练将更为关键,以满足更多后端理解与互动需求
CES 2025,黑芝麻智能携旗下华山系列、武当系列芯片参展,并带来与产业链伙伴的合作新进展。1月8日,黑芝麻智能与汽车嵌入式互联软件产品和解决方案供应商Elektrobit联合发布了基于武当系列C1
点击蓝字 关注我们SUBSCRIBE to USImage: The Verge据悉,OpenAI已经制定了成为一家营利性公司的计划。在近日发布的一篇博客文章中,OpenAI的董事会表示,将把公司现有
随着Mini/Micro LED技术发展和小间距产品成熟,LED显示行业在更多细分场景下的高增长潜力正在加速释放。Mini LED背光市场自2021年进入起量元年后,年复合增长率达50%;Micro
亚化咨询重磅推出《中国半导体材料、晶圆厂、封测项目及设备中标、进口数据全家桶》。本数据库月度更新,以EXCEL表格的形式每月发送到客户指定邮箱。中国大陆半导体大硅片项目表(月度更新)中国大陆再生晶圆项
日前,微信安卓版迎来8.0.56正式版更新,这是2025年首次版本更新。关于更新内容,依然是那9个字:“修复了一些已知问题”。虽然官方没有公布具体更新内容,但体验后发现,新版增加了朋友圈视频倍速播放等
戴尔科技AI PC产品组合助力终端用户释放创造力并提高工作效率。 戴尔科技统一旗下产品组合品牌命名,旨在帮助用户更轻松、快速地找到相匹配的PC、配件及服务。 搭载英
 △广告 与正文无关 1月3日,The Elec援引电子元件专业媒体内容表示,尽管取代中国PCB的努力仍在继续,但预计到2028年,中国(包括大陆和台湾省)在全球PCB销售中的份额将超过60%,在市场