发新帖本帖赏金 50.00元(功能说明)我要提问
12下一页
返回列表
[开发工具]

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

[复制链接]
1095|33
手机看帖
扫描二维码
随时随地手机跟帖
DKENNY|  楼主 | 2024-6-26 14:02 | 显示全部楼层 |阅读模式
本帖最后由 DKENNY 于 2024-6-26 14:23 编辑

#申请原创# @21小跑堂
    在软件开发中,编译器优化选项是提高程序执行效率的关键工具。特别是在嵌入式系统开发中,不同编译器的优化选项可以对代码的性能产生显著影响,本文将探讨Keil编译器中不同优化等级对代码执行效率的关系。

## Keil优化等级说明
  Keil编译器的优化等级是编译过程中一个重要的设置,它可以显著影响程序的性能和大小。

  为什么需要进行编译优化?
  进行编译优化是为了提高程序的性能和效率。编译优化的主要用途包括:
    - 提高执行速度:通过优化代码,减少程序运行时的指令数量,从而加快程序的执行速度。
    - 减少内存占用:优化可以帮助减少程序的大小,降低对内存的需求,这在资源受限的嵌入式系统中尤为重要。
    - 电源管理:优化后的代码可以更有效地使用处理器资源,这有助于降低能耗,延长电池寿命。
    - 提高可靠性和稳定性:优化可以消除代码中的冗余和不必要的部分,有助于提高程序的稳定性和可靠性。

  Keil有几个不同的优化等级:
    - O0这是无优化等级,编译器不会尝试优化代码。这对于调试非常有用,因为生成的代码会非常接近你的原始代码,使得跟踪和理解程序的行为变得容易。
    - O1这个等级提供了基本优化。编译器会尝试不影响代码可读性的前提下,进行一些基本的性能改进。
    - O2在这个等级,编译器会进行更多的优化,如代码重排和循环优化,以提高执行速度和减少代码大小。
    - O3这是最高的优化等级,编译器会尝试所有可能的优化技术来提高程序的运行效率,即使这可能会增加代码的大小。

image001.png


## 测试环境:
    开发板: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得到的代码执行时间。

image003.png

### 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得到的代码执行时间。

image005.png

### 测试数据对比:
  
优化等级
  
Test_add(单位:秒)
Test_fft(单位:秒)
-O0
298.978190
68.000412
-O1
239.971474
62.022487
-O2
179.987680
61.009636
-O3
179.987680
61.985575

### 测试数据分析:
    - 对于简单的加法操作(test_add),随着编译优化等级的提升,执行时间显著减少。特别是从 -O0-O1 ,以及从 -O1-O2,我们看到了明显的性能提升。然而,从 -O2-O3,性能提升不再明显,执行时间保持不变,这表明对于简单运算,-O2 已经足够优化。

    - 对于复杂的运算,如快速傅里叶变换(test_fft),优化等级的提升同样带来了性能的提升,但提升幅度相对较小。从 -O0-O1 有一定的性能提升,而从 -O1-O2,我们看到了最佳的执行时间,但从 -O2-O3,执行时间反而略有增加。

    打个简单的比方,当你让电脑做一些非常基本的事情,比如简单的加法,提高编译器的优化水平就像是给电脑喝了能量饮料,它会变得更快。但是,就像人在喝了太多能量饮料后不会变得更聪明一样,电脑在处理复杂的数学问题时,即使优化了,也不会有太大的提升。这是因为复杂的问题本身就很难处理,就像是需要解决一个超级难的数独谜题,而不仅仅是数数那么简单。
    所以,当你让电脑做更复杂的事情,比如快速傅里叶变换(一种复杂的运算),即使你给它更多的能量饮料(也就是更高级的优化),它也只能快那么一点点。因为这时候,电脑不仅要算数,还要记住很多东西,还要做很多决定,这些都会让它慢下来。
    总之,让电脑做简单的事情时,优化可以大大提高速度。但是对于复杂的任务,优化帮助不大,因为任务本身就很难。

## 总结
    总的来说,随着编译优化等级的提高,简单和复杂的运算都有性能提升,但简单运算的提升更为显著。这可能是因为编译器优化更容易对简单的重复操作进行优化,如循环展开、指令重排等。而对于复杂运算,尽管优化也有帮助,但由于算法本身的复杂性,优化空间可能不如简单运算那么大。此外,复杂运算可能涉及更多的内存访问和分支预测,这些因素也会影响优化的效果。

    附件为该测试的工程源码,有需要的可自行下载。
Compilation_Optimization.zip (788.06 KB)

使用特权

评论回复

打赏榜单

21小跑堂 打赏了 50.00 元 2024-06-27
理由:恭喜通过原创审核!期待您跟多的原创作品~

评论
21小跑堂 2024-6-27 13:23 回复TA
Keil中优化等级变换的代码执行速度问题探讨,作者通过实际测试对比不同优化等级下MCU执行速度的对比,并通过自己的理解巧妙地解释速度的变化和变化程度的问题,十分形象。关于编译器的优化还有更多可探讨的问题,包括内存占用和带来的相关隐患,欢迎各位大佬发挥自己的独特见解。 
学习了

使用特权

评论回复
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 | 显示全部楼层
除了优化等级,还有其他因素会影响程序的运行效率,如算法复杂度、数据结构、编译器版本等。

使用特权

评论回复
发新帖 本帖赏金 50.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

22

主题

38

帖子

4

粉丝