本帖最后由 DKENNY 于 2024-6-26 14:23 编辑
#申请原创# @21小跑堂
在软件开发中,编译器优化选项是提高程序执行效率的关键工具。特别是在嵌入式系统开发中,不同编译器的优化选项可以对代码的性能产生显著影响,本文将探讨Keil编译器中不同优化等级对代码执行效率的关系。
## Keil优化等级说明 Keil编译器的优化等级是编译过程中一个重要的设置,它可以显著影响程序的性能和大小。
为什么需要进行编译优化? 进行编译优化是为了提高程序的性能和效率。编译优化的主要用途包括: - 提高执行速度:通过优化代码,减少程序运行时的指令数量,从而加快程序的执行速度。 - 减少内存占用:优化可以帮助减少程序的大小,降低对内存的需求,这在资源受限的嵌入式系统中尤为重要。 - 电源管理:优化后的代码可以更有效地使用处理器资源,这有助于降低能耗,延长电池寿命。 - 提高可靠性和稳定性:优化可以消除代码中的冗余和不必要的部分,有助于提高程序的稳定性和可靠性。
Keil有几个不同的优化等级: - O0:这是无优化等级,编译器不会尝试优化代码。这对于调试非常有用,因为生成的代码会非常接近你的原始代码,使得跟踪和理解程序的行为变得容易。 - O1:这个等级提供了基本优化。编译器会尝试不影响代码可读性的前提下,进行一些基本的性能改进。 - O2:在这个等级,编译器会进行更多的优化,如代码重排和循环优化,以提高执行速度和减少代码大小。 - O3:这是最高的优化等级,编译器会尝试所有可能的优化技术来提高程序的运行效率,即使这可能会增加代码的大小。
## 测试环境: 开发板:APM32F407 主频:168MHz
### 1.Test_add测试
一个简单的加法运算。
核心代码: void add(u32 n)
{
u32 a = 0, b = 0;
while (n-- > 0)
{
a = b + n;
}
}
//执行100000次
void test_add(void)
{
u32 n = 100000;
while (n-- > 0)
{
add(100000);
}
return;
}
测试结果: 如下图是依次选择-O0,-O1,-O2,-O3得到的代码执行时间。
### 2.Test_FFT测试
这是一个较复杂的运算,FFT代表快速傅里叶变换。
核心代码: #define PI 3.14159265358979323846
#define N 1024 // 定义FFT的点数
typedef struct
{
double real;
double imag;
} complex32;
//typedef unsigned int u32;
int count = 0;
// 位反转置换函数
void bitReversal(complex32 *pSrc, u32 n)
{
u32 i, j, k;
for (i = 1, j = 0; i < n; i++)
{
for (k = n >> 1; k > (j ^= k); k >>= 1)
{
}
if (i < j)
{
complex32 temp = pSrc[i];
pSrc[i] = pSrc[j];
pSrc[j] = temp;
}
}
}
// 迭代FFT函数
void iterativeFFT(complex32 *pSrc, u32 n)
{
// 预先计算Wn数组
complex32 *Wn = (complex32 *)malloc(sizeof(complex32) * n / 2);
for (u32 i = 0; i < n / 2; i++)
{
Wn[i].real = cos(2 * PI * i / n);
Wn[i].imag = -sin(2 * PI * i / n);
}
// 位反转置换
bitReversal(pSrc, n);
// FFT主循环
for (u32 s = 1; s <= log2(n); s++)
{
u32 m = 1 << s;
u32 m2 = m >> 1;
complex32 w;
w.real = 1.0;
w.imag = 0.0;
for (u32 j = 0; j < m2; j++)
{
for (u32 k = j; k < n; k += m)
{
u32 t = k + m2;
complex32 u = pSrc[k];
complex32 v;
v.real = w.real * pSrc[t].real - w.imag * pSrc[t].imag;
v.imag = w.real * pSrc[t].imag + w.imag * pSrc[t].real;
pSrc[k].real = u.real + v.real;
pSrc[k].imag = u.imag + v.imag;
pSrc[t].real = u.real - v.real;
pSrc[t].imag = u.imag - v.imag;
}
complex32 wn = Wn[n / m * j];
w.real = w.real * wn.real - w.imag * wn.imag;
w.imag = w.real * wn.imag + w.imag * wn.real;
}
}
free(Wn);
}
测试结果:
如下图是依次选择-O0,-O1,-O2,-O3得到的代码执行时间。
### 测试数据对比:
### 测试数据分析: - 对于简单的加法操作(test_add),随着编译优化等级的提升,执行时间显著减少。特别是从 -O0 到 -O1 ,以及从 -O1 到 -O2,我们看到了明显的性能提升。然而,从 -O2 到 -O3,性能提升不再明显,执行时间保持不变,这表明对于简单运算,-O2 已经足够优化。
- 对于复杂的运算,如快速傅里叶变换(test_fft),优化等级的提升同样带来了性能的提升,但提升幅度相对较小。从 -O0 到 -O1 有一定的性能提升,而从 -O1 到 -O2,我们看到了最佳的执行时间,但从 -O2 到 -O3,执行时间反而略有增加。
打个简单的比方,当你让电脑做一些非常基本的事情,比如简单的加法,提高编译器的优化水平就像是给电脑喝了能量饮料,它会变得更快。但是,就像人在喝了太多能量饮料后不会变得更聪明一样,电脑在处理复杂的数学问题时,即使优化了,也不会有太大的提升。这是因为复杂的问题本身就很难处理,就像是需要解决一个超级难的数独谜题,而不仅仅是数数那么简单。 所以,当你让电脑做更复杂的事情,比如快速傅里叶变换(一种复杂的运算),即使你给它更多的能量饮料(也就是更高级的优化),它也只能快那么一点点。因为这时候,电脑不仅要算数,还要记住很多东西,还要做很多决定,这些都会让它慢下来。 总之,让电脑做简单的事情时,优化可以大大提高速度。但是对于复杂的任务,优化帮助不大,因为任务本身就很难。
## 总结 总的来说,随着编译优化等级的提高,简单和复杂的运算都有性能提升,但简单运算的提升更为显著。这可能是因为编译器优化更容易对简单的重复操作进行优化,如循环展开、指令重排等。而对于复杂运算,尽管优化也有帮助,但由于算法本身的复杂性,优化空间可能不如简单运算那么大。此外,复杂运算可能涉及更多的内存访问和分支预测,这些因素也会影响优化的效果。
附件为该测试的工程源码,有需要的可自行下载。
|
Keil中优化等级变换的代码执行速度问题探讨,作者通过实际测试对比不同优化等级下MCU执行速度的对比,并通过自己的理解巧妙地解释速度的变化和变化程度的问题,十分形象。关于编译器的优化还有更多可探讨的问题,包括内存占用和带来的相关隐患,欢迎各位大佬发挥自己的独特见解。