DKENNY 发表于 2024-6-26 14:02

探讨Keil中优化等级与运行效率关系

本帖最后由 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;
            pSrc = pSrc;
            pSrc = 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.real = cos(2 * PI * i / n);
      Wn.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;
                complex32 v;
                v.real = w.real * pSrc.real - w.imag * pSrc.imag;
                v.imag = w.real * pSrc.imag + w.imag * pSrc.real;
                pSrc.real = u.real + v.real;
                pSrc.imag = u.imag + v.imag;
                pSrc.real = u.real - v.real;
                pSrc.imag = u.imag - v.imag;
            }
            complex32 wn = Wn;
            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(单位:秒)Test_fft(单位:秒)
-O0298.97819068.000412
-O1239.97147462.022487
-O2179.98768061.009636
-O3179.98768061.985575

### 测试数据分析:    - 对于简单的加法操作(test_add),随着编译优化等级的提升,执行时间显著减少。特别是从 -O0 到 -O1 ,以及从 -O1 到 -O2,我们看到了明显的性能提升。然而,从 -O2 到 -O3,性能提升不再明显,执行时间保持不变,这表明对于简单运算,-O2 已经足够优化。
    - 对于复杂的运算,如快速傅里叶变换(test_fft),优化等级的提升同样带来了性能的提升,但提升幅度相对较小。从 -O0 到 -O1 有一定的性能提升,而从 -O1 到 -O2,我们看到了最佳的执行时间,但从 -O2 到 -O3,执行时间反而略有增加。
    打个简单的比方,当你让电脑做一些非常基本的事情,比如简单的加法,提高编译器的优化水平就像是给电脑喝了能量饮料,它会变得更快。但是,就像人在喝了太多能量饮料后不会变得更聪明一样,电脑在处理复杂的数学问题时,即使优化了,也不会有太大的提升。这是因为复杂的问题本身就很难处理,就像是需要解决一个超级难的数独谜题,而不仅仅是数数那么简单。    所以,当你让电脑做更复杂的事情,比如快速傅里叶变换(一种复杂的运算),即使你给它更多的能量饮料(也就是更高级的优化),它也只能快那么一点点。因为这时候,电脑不仅要算数,还要记住很多东西,还要做很多决定,这些都会让它慢下来。    总之,让电脑做简单的事情时,优化可以大大提高速度。但是对于复杂的任务,优化帮助不大,因为任务本身就很难。
## 总结    总的来说,随着编译优化等级的提高,简单和复杂的运算都有性能提升,但简单运算的提升更为显著。这可能是因为编译器优化更容易对简单的重复操作进行优化,如循环展开、指令重排等。而对于复杂运算,尽管优化也有帮助,但由于算法本身的复杂性,优化空间可能不如简单运算那么大。此外,复杂运算可能涉及更多的内存访问和分支预测,这些因素也会影响优化的效果。
    附件为该测试的工程源码,有需要的可自行下载。



虚幻的是灵魂 发表于 2024-7-2 08:54

学习了

caigang13 发表于 2024-7-4 07:47

不能乱优化,有时候会适得其反。

bartonalfred 发表于 2024-7-4 21:18

优化等级会影响程序的运行效率,但并非总是直接对应。

averyleigh 发表于 2024-7-7 14:09

在开发阶段,你可能希望使用较低的优化等级以方便调试;而在产品化阶段,为了提高性能和减少功耗,你可能需要使用较高的优化等级。

wwppd 发表于 2024-7-7 15:44

优化等级越高,编译器越倾向于生成更小、更快、更高效的代码,但同时也可能增加编译时间和调试难度。

olivem55arlowe 发表于 2024-7-9 20:48

低级别的优化保留更多的调试信息,使代码在调试时更容易理解和追踪。

plsbackup 发表于 2024-7-9 23:51

在实际应用中,通常需要在程序大小和运行速度之间做出权衡。例如,嵌入式系统可能更注重程序的大小,以便节省内存资源;而性能敏感的应用则可能更注重程序的运行速度。

jackcat 发表于 2024-7-10 02:51

在Keil编译器中,优化等级与运行效率之间存在着密切的关系。优化等级是编译过程中一个重要的设置,它可以显著影响程序的性能和大小

tifmill 发表于 2024-7-10 05:51

在Keil MDK(Microcontroller Development Kit)中,编译器的优化等级对生成的代码的运行效率有着直接的影响

i1mcu 发表于 2024-7-10 21:14

优化等级1和2都会提高程序的运行效率,但它们的方式不同。等级1通过减少程序体积来提高效率,而等级2通过优化代码运行速度来提高效率。

maqianqu 发表于 2024-7-11 00:22

优化可能会改变代码的结构和顺序,有时会让生成的汇编代码难以与源代码对应,影响调试和维护。

wengh2016 发表于 2024-7-12 17:25

高级别的优化往往会导致更短的执行时间和更小的代码尺寸,但这通常是以牺牲编译时间和调试信息的可读性为代价的。

wangdezhi 发表于 2024-7-12 20:32

Keil编译器(包括Keil C51和针对ARM的Keil MDK等)提供了多个优化等级,从低到高主要包括:

-O0(无优化):不进行任何优化,主要用于调试和开发阶段。代码生成快,但生成的代码质量较低,程序执行效率较慢,且生成的代码体积可能较大。
-O1(基本优化):提供基本的优化,如去除未引用的代码和常量折叠等,以提高程序执行效率。相对于-O0,程序执行效率更高,但代码大小可能略有增加。
-O2(中等优化):进行更多的优化,如代码重排和循环优化,以进一步提高执行速度和减少代码大小。
-O3(高级优化):最高的优化等级,尝试所有可能的优化技术来提高程序的运行效率,即使这可能增加代码的大小。从-O2到-O3,优化可能会更加激进,如更复杂的循环展开和函数内联。

1988020566 发表于 2024-7-13 09:04

在选择优化等级时,需要权衡以下因素:

调试需求:如果需要频繁调试,可能需要降低优化等级以保持代码的可调试性。
性能要求:如果对运行效率有严格要求,可以选择更高的优化等级。
编译时间:高优化等级会增加编译时间,这在开发过程中可能会影响迭代速度。
目标硬件:不同的微控制器可能有不同的最佳优化策略,需要根据硬件特性选择合适的优化等级。

juliestephen 发表于 2024-7-13 12:32

优化等级的选择也应该考虑目标平台的硬件资源。在资源有限的平台上,选择适当的优化等级以避免过度优化导致的资源浪费。

jonas222 发表于 2024-7-13 19:41

无优化(Optimization Level 0):
不进行任何优化,保留原始代码的所有细节。
适合调试,因为生成的代码与源代码一一对应,便于定位问题。
运行效率最低,因为代码可能包含冗余和不必要的操作。
优化级别1(Optimization Level 1):
基本的优化,如删除未使用的代码、简化表达式等。
对代码结构的影响较小,仍保持较好的可读性和可调试性。
运行效率有所提升,但提升幅度不大。
优化级别2(Optimization Level 2):
中级优化,包括循环展开、寄存器分配优化、内联函数等。
代码结构可能会有较大变化,调试时可能难以跟踪到源代码行。
运行效率显著提升,代码执行速度加快,内存占用减少。
优化级别3(Optimization Level 3):
高级优化,包括更多的循环优化、指令调度、复杂的数据流分析等。
代码结构变化更大,调试更加困难,可能无法精确映射到源代码。
运行效率进一步提升,但提升幅度相比级别2可能不会太大。
优化级别4(Optimization Level 4):
最高级别的优化,可能会引入一些激进的优化策略,如代码重排序、复杂的数据流和控制流分析等。
代码结构可能与源代码差异很大,几乎不可调试。
运行效率最高,但编译时间最长。

sdlls 发表于 2024-7-15 10:39

较高的优化等级通常能够带来更好的运行效率,但也可能使得调试变得更加困难。

juliestephen 发表于 2024-7-15 14:01

等级0(No Optimization):此等级不进行任何优化,主要用于调试。
等级1(Size Optimization):此等级主要优化程序的大小,通过删除未使用的代码和数据来减少程序的体积。
等级2(Speed Optimization):此等级主要优化程序的运行速度,通过优化代码结构和使用更高效的算法来提高执行效率。
等级3(Speed and Size Optimization):此等级试图在保持程序运行速度的同时,尽可能减少程序的大小。

cemaj 发表于 2024-7-15 17:28

除了优化等级,还有其他因素会影响程序的运行效率,如算法复杂度、数据结构、编译器版本等。
页: [1] 2 3
查看完整版本: 探讨Keil中优化等级与运行效率关系