实战技能分享,如何让工程代码各种优化等级通吃,含MDK AC5,AC6,IAR和GCC
引出问题:
一个好的工程项目代码,特别是开源类的,如果能做到各种优化等级通吃,是一种非常好的工程案例,这样别人借鉴的时候,可以方便的适配到自己工程里。但实际项目中,针对一款产品代码,我们一般不会这么干,因为非常耗精力,意义也不大,一般是追求最高性能,最小代码量或者更高的稳定性,我们会选择一个合理的优化等级。
但是随着工程的复杂,特别是一些第3方组件的加入,很容易碰到不耐优化的情况。也就是这个组件没法适配到我们当前的优化等级里面。甚至有时候我们还会遇到高优化等级能用,改成0级优化反倒不能用了。
本期帖子我们就分享一种方法来解决这个问题,合理的设置不同代码的不同优化等级,即一种优化为主优化等级,其它代码设置到能用的优化等级上,以此来达到通吃的目的。
如果采用这种办法可以一步一步的锁定具体问题所在,并将工程文件全部设置到同一个优化等级是最好的。
MDK设置方法(AC5和AC6):
分两个方向:
1、开启优化后,部分功能不正常
解决思路是把这部分的文件继续设置为低优化等级,整体工程设置为高优化等级(这种方法可以锁定有问题的文件,然后锁定具体有问题的函数)。
2、开启优化后,直接整体卡死
解决思路是整体工程设置为低优化等级,逐步开启工程文件的优化等级。具体到某些函数的优化也是可以单独开启测试的。
AC5设置方法:
比如设置函数优化等级为0
https://www.keil.com/support/man ... hr1359124988971.htm
#pragma push #pragma O0 void function(void){ ... // Optimized at O0 } #pragma pop
AC6设置方法:
这里设置无优化
void function(void) _attribute__((optnone)) { ... // Optimized none }
IAR设置方法:
IAR和MDK的设置是一样的,同样我们也分为两个方向:
1、开启优化后,部分功能不正常
解决思路是把这部分的文件继续设置为低优化等级,整体工程设置为高优化等级(这种方法可以锁定有问题的文件,然后锁定具体有问题的函数)。
2、开启优化后,直接整体卡死
这种的解决思路是整体工程设置为低优化等级,逐步开启工程文件的优化等级。具体到某些函数的优化也是可以单独开启测试的。
比如设置函数无优化:
https://netstorage.iar.com/SuppDB/Public/UPDINFO/004916/arm/doc/EWARM_DevelopmentGuide.ENU.pdf (253页)
#pragma optimize=none void foo(void) { /* Do something, but don't optimize this function */ }
GCC设置方法:
GCC的话,我们这里以Embedded Studio为例进行说明,同样我们也分为两个方向:
1、开启优化后,部分功能不正常
解决思路是把这部分的文件继续设置为低优化等级,整体工程设置为高优化等级(这种方法可以锁定有问题的文件,然后锁定具体有问题的函数)。
2、开启优化后,直接整体卡死
这种的解决思路是整体工程设置为低优化等级,逐步开启工程文件的优化等级。具体到某些函数的优化也是可以单独开启测试的。
比如设置函数无优化:
#pragma GCC push_options #pragma GCC optimize ("O0") void foo(void) { /* Do something, but don't optimize this function */ } #pragma GCC pop_options
不同优化最容易出问题的地方:
延迟类函数最容易出问题,特别是像for循环这种简单实现的延迟。可以考虑使用DWT时钟周期计数器做延迟。
http://www.armbbs.cn/forum.php?mod=viewthread&tid=89128
不迷信编译器:
即使再强劲的编译器,有触摸不到的天花板。
MDK AC6的0级优化在这方面的设计问题最明显。比如MDK AC6.14使用0级优化编译HAL库的n级条件表达式会产生巨大的栈需求。
现象:
使用MDK5.30 AC6.14的0级优化测试RTX5的模板程序,发现启动任务需要高达2000字节的栈需求。
原因分析:
通过不断的调试和查看map,htm等文件,最终锁定是H7的HAL库函数UART_SetConfig导致的。
进一步的排查,锁定是下面这种n级条件表示导致的,下面这种类型的表达式偏偏在函数UART_SetConfig里面有一大批,导致产生巨大的栈需求。
/** @brief Get UART clok division factor from clock prescaler value. * @param __CLOCKPRESCALER__ UART prescaler value. * @retval UART clock division factor */ #define UART_GET_DIV_FACTOR(__CLOCKPRESCALER__) \ (((__CLOCKPRESCALER__) == UART_PRESCALER_DIV1) ? 1U : \ ((__CLOCKPRESCALER__) == UART_PRESCALER_DIV2) ? 2U : \ ((__CLOCKPRESCALER__) == UART_PRESCALER_DIV4) ? 4U : \ ((__CLOCKPRESCALER__) == UART_PRESCALER_DIV6) ? 6U : \ ((__CLOCKPRESCALER__) == UART_PRESCALER_DIV8) ? 8U : \ ((__CLOCKPRESCALER__) == UART_PRESCALER_DIV10) ? 10U : \ ((__CLOCKPRESCALER__) == UART_PRESCALER_DIV12) ? 12U : \ ((__CLOCKPRESCALER__) == UART_PRESCALER_DIV16) ? 16U : \ ((__CLOCKPRESCALER__) == UART_PRESCALER_DIV32) ? 32U : \ ((__CLOCKPRESCALER__) == UART_PRESCALER_DIV64) ? 64U : \ ((__CLOCKPRESCALER__) == UART_PRESCALER_DIV128) ? 128U : \ ((__CLOCKPRESCALER__) == UART_PRESCALER_DIV256) ? 256U : 1U)
解决办法:
使用AC6中0以外的其它优化就解决了,或者使用AC5的任何优化等级也都可以解决。
又比如:
如果用AC6的优化等级0,没有选择使用微库的话(底层做了C标准库重定向),偶尔会造成脱机(调试仿真下可以使用,拔掉下载器运行就失败)执行失败,将微库勾上即可解决:
这坑也非常容易遇到。
各种优化等级通吃的实战案例分享:
那么问题来了,有没有不需要设置不同优化等级的综合Demo分享? 有的,早期为V6板子设计的二代示波器Demo,可以各种优化等级通吃,并且开启了时间优化。无需采用本帖的特别设置方法,直接切换优化等级就可以使用,大家有兴趣可以看看工程代码:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=45785
实际项目中让程序代码在所有优化等级下都可以正常运行来检查各种奇葩问题,也是一种非常有效的检测手段,确实可以找到程序里面的一些隐形bug。