广告

如何编写有利于编译器优化的代码

时间:2021-11-12 作者:IAR Systems 阅读:
在嵌入式开发中,代码的体积和运行效率非常重要,代码体积往往和芯片的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

本文为EET电子工程专辑 原创文章,禁止转载。请尊重知识产权,违者本司保留追究责任的权利。
  • 北交所的“芯”未来(EDA) 当前,半导体行业成为科技行业的新风口,且受到国家的高度关注,北交所的快速成立与火速开市就体现了国家政策对半导体行业在金融方面的支持。长期以来,回报周期长,利润率低,技术难度大的半导体细分产业链中的中小企业的融资非常困难……
  • 我们又搭了一台平价PC:体验12代酷睿CPU 此前对于12代酷睿两种不同核心微架构,处理器理论性能,以及配套12代酷睿出现的核心调度技术(Intel Thread Director),我们都已经做过比较完整的解读。这篇文章就从三方的角度,来简单谈谈12代酷睿处理器的使用体验,算是提供选购和理解当代PC处理器的参考。
  • EDA全球冠军!ICCAD 2021华中科技大学战队夺CAD Contest 在11月4日结束的EDA领域国际会议ICCAD 2021上,华中科技大学计算机学院吕志鹏教授团队获得了CAD Contest布局布线(Routing with Cell Movement Advanced)算法竞赛的第一名。据悉,今年是该团队首次参加ICCAD竞赛,成员非常年轻,平均年龄仅24岁,包括苏宙行博士,硕士研究生罗灿辉、梁镜湖和谢振轩……
  • 5G SA初始部署后要如何发展?Omdia谈5G SBA格局 鉴于截至2021年下半年,仅有不到10家运营商推出了商用5G SA服务,随着2022年SBA部署在5G SA中加速,以及一些标准推动SBA理念用于5G网络核心之外的领域,对于大多数运营商及其供应商来说,现在是时候去了解SBA对其网络和业务意味着什么了。
  • 为加速AI落地企业IT,英伟达布下一盘超大棋局 NVIDIA EGX平台将计算和图形加速、高速安全网络和企业级管理引入到领先的企业数据中心服务器中,支持大量加速应用程序,为客户提供了一种在高性能、经济高效且可扩展的统一基础架构上运行各种传统和现代应用的方式,使用户能够立即提高产品化效率。
  • 4个问题,来谈谈OpenHarmony工业项目是否靠谱 我们在拿到有关OHI项目的资料时,感觉其明确的愿景和目标都非常庞大。做工业操作系统以及相关软件组件的开源开放,形成通用的开源软件组件,以及扩展生态、为OpenHarmony贡献代码,这些都算是此类项目的常规目标。
  • 新款iPad Pro 2021成最受欢迎的 由于采用性能相对强大的M1处理器和mini-LED屏幕以及更多的创新,新款iPad Pro 2021已经成为消费者心目中最受欢迎。然而,iPad 2却已经在全球范围内被列入“复古和过时”的名单中。
  • 三星折叠屏手机Galaxy Z Fold 3 目前来看,折叠屏新机作为一种新的生产力工具,逐渐成为高端/平板的一种趋势,有报料称三星的Galaxy Z Fold 3发布时间或为7月,并且会引入新手势操控。

  • 储能与电动汽车应用爆发下,安全可靠 随着汽车设计转向电气化,以及风能和太阳能等可再生能源的部署速度加快,并不断与新推出的储能和电池技术融合。高功率电子成为电池系统的关键部件。这些电子需要与低压数字控制器通信并由其控制,如何实现安全迅速的接口通信是设计可靠电池管理系统的一大挑战。
  • 中国芯应用创新32强出击,众多奖项花 11月16日,第三届IAIC中国芯应用创新设计大赛决赛在深圳前海举行,大赛组委会邀请了来自兆易创新、华大半导体等原厂专家、来自旦恩资本、一本基金、深创投等资深投资机构以及来自中电港、中科院深圳先进院、深半协、深圳中微电、健天电子、史河机器人科技、亚力盛等行业专家作为决赛的评委专家组。
  • 南大光电ArF光刻胶已建成两条生产线! 点击上方图片直接报名会议南大光电在11月24日接受调研时表示,ArF光刻胶方面,每个客户都有三四款胶在做认证。光刻胶的认证一般需要一年以上的时间,经历四轮以上的送样。目前已经建成两条生产线,具备25吨
  • 高精度运放 高精度运放品牌:E-CMOS     型号:EC5462AR-G(替代AD8052)类型:双通道运放封装:MSOP-8数量:600K品质:全新原包可替代AD8052联系方
  • 特斯拉 | 总投资12亿元!上海工厂再度扩产,明年4月完工 来源 :新京报11月26日,从上海企事业单位环境信息公开平台获悉,特斯拉对上海超级工厂(一期)第二阶段的产线优化项目进行环评公示。环评报告显示,该产线优化项目投资总额高达12亿元人民币,其中
  • 亚化咨询半导体研究系列报告 欢迎征订!如需索取目录欢迎联系亚化咨询朱经理MP: 17717602095(微信同号)Email: rita@asiachem.org
  • 做AGV 20年,机科有话说 机科如何定位自身,又如何理解行业?文|新战略作为国内最早一批入局AGV行业的企业,机科早在1999年便开始智能输送装备相关情况调研,2002年,由“机械科学研究院”相关研究所转制正式成立“机科发展股份
  • 最新!美光和联电和解 11月26日,美光科技与联电共同宣布,两家公司在全球范围内达成和解协议。两家公司将在全球范围内撤回对另一方的投诉,联电将一次性向美光支付一笔未公开的金额。联电和美光期待开展相互的商业合作机会。此案源于
  • 极智嘉携手九州通打造全球首个AMR月台集货项目! 创新型、高效率、智能化!文|极智嘉全球AMR引领者极智嘉(Geek+)携手医药龙头企业九州通成功落地全球首个AMR月台集货。通过跨楼层、跨库区的综合性解决方案,极智嘉在九州通郑州物流中心近万平场地部署
  • 动图了解PCB整个古老制作过程! PCB( Printed Circuit Board),中文名称为印制电路板,是电子元器件的支撑体。由于它是采用电子印刷术制作的,故被称为“印刷”电路板。在PCB出现之前,电路是通过点到点的接线组成的
  • 倒计时十一天!免费参观海宁泛半导体产业园!第四届中国半导体大硅片论坛即将召开! 点击上方图片直接报名会议硅片是IC生产的主要原材料。亚化咨询数据显示,2020年全球半导体硅片市场达到126.29亿美元,相较于2019年有较快增长。其中全球前四大供应商,Shin-Etsu、SUMC
  • 中国PCB百强! ▼2020年度中国综合PCB百强排行榜▼2020年度中国内资PCB百强排行榜▼2020年度中国PCB覆铜箔板企业排行榜▼2020年度中国PCB专用材料企业排行榜▼2020年度中国PCB专用化学品企业排
广告
热门推荐
广告
广告
广告
EE直播间
在线研讨会
广告
广告
广告
向右滑动:上一篇 向左滑动:下一篇 我知道了