C语言编译的优化等级应该选哪个?O0、O1、O2还是O3

在使用IDE开发STM32程序时,IDE一般都会提供优化等级设置的选项,例如下图中KEIL软件优化等级的设置。

在这里插入图片描述
从上图中也可以看出,设置不同的优化等级,实际上是修改了编译器的编译参数。这个编译器是由ARM公司提供的C/C++编译器armclang或者armcc。编译器提供了不同的优化等级,能够优化由用户代码生成的目标代码。这里的优化主要针对4个方面:

  • 代码的运行效率
  • 生成的目标代码的体积
  • 调试信息是否完整准确
  • 生成目标代码的构建时间长短

针对以上4点,编译器提供了不同的参数,以满足不同的需求,见下表所示
优化目标与参数
从上表可知,在keil软件中能够选择的O0、O1、O2和O3,针对的是目标代码的运行效率,等级越高,对目标代码的优化就越多,运行效率就越高。不过在提高了运行效率的同时,会使其它几个方面的变差,比如会增加目标代码的体积、调试信息失真,以及需要更长的编译时间。

下面针对O0、O1、O2和O3这几个等级,分别介绍它们的特点和区别。(部分涉及到编译优化方法的名词会在最后的附录中进行解释,遇到不理解的名词可以查看文章最后的附录)

O0等级

该等级为最小的优化等级,关闭了大多数的优化措施。这种情况生成的固件和用户编写的代码几乎是对应的,因此用户在调试代码的时候,能够根据源代码更快的定位到编译后的位置。

在O0这个优化等级下,不会对代码进行优化,不会删除用户的死代码(dead code,死代码指的是编写了但是没有用到的代码,也包括不起作用的代码),不会删除没有使用的变量,能够读到各个函数完整的栈信息(不理解这句话的,可以向下看O1等级的第4条,对比一下就理解了)。

O1等级

该等级实现受限的优化。在这个模式下会对用户代码进行优化,同时尽量不影响用户的调试信息。与O0的不同之处如下:

  1. 调试的断点不能设置到死代码上,因为死代码会被优化掉;
  2. 有的临时变量,虽然还在其作用域中,但是也会被清除,比如这个变量在后面没有再用到,而且其所在的栈的位置被调用的子函数占用了,那么在返回时这个变量可能不会恢复。
  3. 死代码也就是没有作用的代码会被移除,比如没有被调用的函数,或者这个函数对外部没有任何影响的(no side-effects ),以下面这个代码为例,这个函数用到的全是临时变量,而且也没有返回值的,那么这个函数就不会对外接产生任何影响,也就认为没有任何作用,就会被优化掉。
void add_fake(){int a=1,b=2;int c = a+b;
}
  1. 由于尾调用的存在,回溯(backtrace)可能无法提供从源代码阅读中期望的函数调用栈。尾调用如下面的代码中语句return add(a,b),这个语句就是尾调用,尾调用是一种在函数末尾直接调用另一个函数的优化技术,它的特点是当前函数在调用另一个函数后不会保留任何上下文或返回地址,而是直接跳转到被调用函数的执行。在支持尾调用优化的编程语言或环境中,这种优化可以减少栈帧的数量,提高程序的执行效率。然而,正因为尾调用优化的存在,当程序出现错误或需要回溯时,传统的回溯技术可能无法准确地展示函数调用的完整栈。这是因为尾调用不会创建新的栈帧,而是重用了当前栈帧,这可能导致回溯结果中缺少某些函数调用的信息,或者函数的调用顺序看起来与源代码中的顺序不符。
void test(){int a=1,b=2;return add(a,b);
}

在这个优化等级下,进行的优化比较少,主要是针对死代码和没有用的变量进行了优化,生成的目标代码的结构和源代码区别不大,因此对用户的调试没多大影响。而且因为O1删除了死代码,因此其生成的代码体积要小于O0等级的。

O2等级

高等优化。这个等级优化后的目标代码和源代码不会一一对应,这是因为使用了类似于循环展开、程序内联、常量折叠、公共子表达式消除、死代码消除、向量化等编译优化措施,优化了生成的代码结构。因此在调试的时候可能会发现无法很好的定位到源代码对应的目标代码的位置,不利于对源代码的调试。O2等级使用了O1等级的所有优化方法,同时还使用了:

  1. 由于源代码中的多个位置可能映射到目标代码中的同一个点,以及编译器可能进行的更激进的指令调度,源代码到目标代码的映射可能是多对一的关系。这意味着从目标代码回溯到源代码可能不是直接的或明确的。
  2. 编译器在优化过程中进行的指令调度,如果允许跨越序列点,可能会导致在特定点报告的变量值与直接从源代码中预期的值之间存在不匹配。因此在分析编译后的代码或调试程序时,需要特别注意编译器优化可能引入的这种复杂性。
  3. 编译器会自动内联函数。

O3等级

这个等级会对用户代码进行最大的优化。这个等级下的优化除了包含O2等级的优化,还包含:

  1. 通过包括循环展开在内的高级标量优化,可以在较小代码尺寸代价下获得显著的性能提升。通过循环展开和其他高级标量优化,通常可以获得显著的性能提升,因为减少了循环控制和分支预测的开销,并可能提高了数据访问的局部性。但也会增加编译生成目标代码的构建时间。这要求开发者在权衡性能提升和构建时间之间做出决策。
  2. 更激进的内联和自动内联。编译器会更加积极地选择更多的函数进行内联,而不仅仅是那些显然可以带来性能提升的函数。这种更激进的做法可能会增加代码的大小,但通常可以带来更好的性能。编译器通常会基于一系列因素(如函数大小、调用频率、函数的复杂度等)来决定是否要进行内联。

编译器对代码的优化,会使目标代码的结构和源代码的结构相差很大,因此使得用户在通过源代码进行调试的时候,有更糟糕的体验。因此ARM公司不推荐在这种优化等级下使用调试功能。

总结

从上面的内容可以知道,更高的优化等级会使用更多的编译优化技术,对用户的源代码进行优化,从而使得生成的目标代码的结构与源代码的结构区别很大,这样就不利于用户对代码的调试。

因此在前期编写代码时,最好将优化等级设置为O0和O1,这样能够更方便的进行调试,相比于O0,ARM公司更推荐使用O1等级,因为O1等级会进行一部分优化而且对调试影响不大。而后期要交付产品的时候,为了追求代码的运行效率,可以将优化等级调整到O2和O3。

附录-常用的编译优化手段

在上面的文章中,提到了很多的编译优化方法,平时没接触处过编译的知识,所以会不好理解,下面对上面提到的优化方法,以及一些其它的常用优化方法进行列举和解释。

  1. 尾调用是一种在函数末尾直接调用另一个函数的优化技术,它的特点是当前函数在调用另一个函数后不会保留任何上下文或返回地址,而是直接跳转到被调用函数的执行。在支持尾调用优化的编程语言或环境中,这种优化可以减少栈帧的数量,提高程序的执行效率。然而,正因为尾调用优化的存在,当程序出现错误或需要回溯时,传统的回溯技术可能无法准确地展示函数调用的完整栈。这是因为尾调用不会创建新的栈帧,而是重用了当前栈帧,这可能导致回溯结果中缺少某些函数调用的信息,或者函数的调用顺序看起来与源代码中的顺序不符。
  2. 源代码到目标代码的映射:在编译过程中,源代码(即程序员编写的代码)会被转换成目标代码(即机器可以直接执行的代码)。这种转换通常是通过一系列步骤完成的,包括词法分析、语法分析、语义分析、优化和代码生成等。
  3. 多个源代码位置映射到一个目标代码点:这通常发生在编译器进行优化时。例如,某些在源代码中明显分开的操作可能在目标代码中合并成一个。或者,源代码中的某些变量或常量可能在编译时被优化或消除,导致它们在目标代码中不再有明显的对应。
  4. 指令调度是编译器优化技术的一种,其主要目的是重新排列指令以提高执行效率。更激进的指令调度可能意味着编译器会更大胆地重新排列或合并源代码中的指令,以生成更高效的目标代码。这同样可能导致源代码和目标代码之间的映射关系变得复杂。
  5. 指令调度允许跨越序列点:在编译过程中,指令调度是一种优化技术,用于重新安排程序中指令的执行顺序,以改善程序性能。序列点(sequence points)是程序中特定的点,在这些点上,所有之前的副作用(如变量值的改变)都必须完成,并且这些变化对后续代码可见。然而,在某些情况下,编译器可能会进行更激进的优化,允许指令跨越这些序列点进行调度。
  6. 可能导致变量值的不匹配:当指令调度跨越序列点时,这可能会导致在源代码中看起来应该按特定顺序执行的操作在实际的目标代码中不再保持这个顺序。因此,如果在某个特定点检查一个变量的值,可能会发现该值与从源代码直接阅读时所期望的值不匹配。
  7. 内联函数(Inline Function)是一种编译器优化技术,用于减少函数调用的开销。当一个函数被声明为内联时,编译器会尝试在调用该函数的地方直接插入(或“内联”)该函数的代码,而不是进行常规的函数调用。这可以消除函数调用的开销,包括参数传递、栈帧的创建和销毁等,从而可能提高程序的执行效率。当编译器“自动内联函数”时,它会自动决定哪些函数应该被内联,而不需要程序员显式地指定。这通常基于函数的尺寸、调用频率和其他一些因素。需要注意的是,过度使用内联可能会导致代码膨胀,从而可能增加指令缓存的未命中率和其他负面影响,所以编译器在内联函数时会进行权衡。同时,有些函数可能不适合内联,例如那些有循环或复杂控制流的函数。因此,尽管编译器可以自动内联函数,但程序员仍然需要了解内联的优缺点,并在必要时通过编译器选项或特定的代码标记来控制内联行为。
  8. 高级标量优化:标量优化是针对单个变量或数据项的优化,而不是针对向量或数组。高级标量优化通常涉及复杂的算法转换和代码变换,以改进程序的执行效率。
  9. 循环展开:循环展开是一种常用的优化技术,它通过减少循环次数并复制循环体中的代码,来减少循环的控制开销。例如,一个原本执行四次迭代的循环可以被展开成四份独立的代码,每份代码执行一次原循环体中的操作。
  10. 常量折叠:在编译时,如果编译器能够确定某个表达式的结果是一个常量,那么它会在编译时直接计算出这个常量值,并将其替换到代码中的相应位置。
  11. 常量传播:如果一个变量的值在编译时已知且是常量,编译器可能会用这个常量值替换掉所有对该变量的引用。
  12. 公共子表达式消除:如果一个表达式在程序中多次出现,并且每次出现时其值都没有变化,编译器会识别出这是一个公共子表达式,并只计算一次,存储结果,然后在需要的地方使用这个存储的结果。
  13. 死代码消除:编译器会检测代码中永远不会被执行的部分(死代码),并将其从最终的可执行文件中移除。
  14. 复写传播:编译器会识别出两个或多个变量实际上持有相同的值,并用一个变量来替换它们。
  15. 指令重排:编译器会重新排列指令的顺序,以减少数据依赖、提高缓存利用率或利用处理器的指令并行能力。
  16. 寄存器分配:编译器会智能地为变量分配寄存器,以减少内存访问次数,提高程序性能。
  17. 轮廓分析(Profile Guided Optimization):编译器使用程序运行时的轮廓信息(例如,哪些代码路径被频繁执行)来指导优化过程。这通常涉及到在程序运行时收集数据,并在后续的编译过程中使用这些数据。
  18. 类型优化:编译器可能会利用类型信息来优化代码,例如通过减少不必要的类型转换或使用特定的指令集来优化特定类型的数据处理。
  19. 向量化和并行化:编译器会尝试将代码转换为向量操作(即一次处理多个数据元素),并利用多核处理器进行并行处理,以提高计算密集型任务的性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/4604.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微信小程序Vue+nodejs+uniapp课堂教学辅助在线学习系统

uni-app框架:使用Vue.js开发跨平台应用的前端框架,编写一套代码,可编译到Android、小程序等平台。 后台主要实现功能:一、用户的管理(用户的信息管理) 二、 课程的管理(课程发布,课后成绩的查看&#xff0c…

DAY 5

1. 2. #include <iostream>using namespace std; class Person {string name;int *age;public:Person():name("zhangsan"),age(new int (18)){cout << "Person的无参函数" << endl;}Person(string name,int *age):name("zhangsan&q…

Linux提权--准备工作知识点工具

目录 知识点: 系列内容&#xff1a; 截至目前思路点总结如下&#xff1a; 思考点&#xff1a; 探针&漏扫工具网址 工具小总: 数据库提权工具网址: ---提权命令百科-- 演示案例&#xff1a; 知识点: 1、Linux 提权辅助项目-探针&漏扫 2、Linux 提权-配置 SUID …

模拟LinkedList实现的双向循环链表

1. 前言 前文我们分别实现了不带哨兵的单链表&#xff0c;带哨兵节点的双向链表&#xff0c;接着我们实现带哨兵节点的双向循环链表.双向循环链表只需一个哨兵节点&#xff0c;该节点的prev指针和next指针都指向了自身哨兵节点. 2. 实现双向循环链表的代码 例 : //模拟双向…

c++中的__declspec(dllexport) 和 __declspec(dllimport)

c中的__declspec(dllexport) 和 __declspec(dllimport) 1. __declspec(dllimport) __declspec(dllimport) 是Microsoft Visual C特有的修饰符&#xff0c;用于声明在动态链接库&#xff08;DLL&#xff09;中定义的函数和变量&#xff0c;以便在另一个模块中使用。它告诉编译…

水稻病害检测(YOLO数据集,多分类,稻瘟病、纹枯病、褐斑病、枯心病、霜霉病、水稻细菌性条纹斑病、稻苞虫)

是自己利用LabelImg工具进行手工标注&#xff0c;数据集制作不易&#xff0c;请尊重版权&#xff08;稻瘟病、纹枯病、褐斑病、枯心病、霜霉病、水稻细菌性条纹斑病、稻苞虫&#xff09; 如果需要yolv8检测模型和数据集放在一起的压缩包&#xff0c;可以关注&#xff1a;最新最…

IO流体系

一.分类 1.字节流 &#xff08;1&#xff09;.InputStream&#xff08;字节输入流&#xff09; 定义&#xff1a;操作本地文件的字节输入流&#xff0c;可以把本地文件中的数据读取到程序中 书写步骤&#xff1a;1.创建字节输入流对象&#xff0c;2.读数据&#xff0c;3.释放…

聊聊Flink:Docker搭建Flink

一、准备工作 查看下Docker和Docker Compose版本&#xff0c;确保你安装了这些软件。 在 Flink 官网上下载 Flink 的 Docker 镜像。您可以使用以下命令从 Docker Hub 中下载&#xff1a; docker pull flink:1.18.0-scala_2.12 此命令将下载 Flink 1.18.0 版本的 Docker 镜像…

Java23种设计模式-创建型模式之单例模式

单例模式&#xff08;Singleton Pattern&#xff09;&#xff1a;通过单例模式的方法创建的类在当前进程中只有一个实例&#xff08;根据需要&#xff0c;也有可能一个线程中属于单例&#xff0c;如&#xff1a;仅线程上下文内使用同一个实例&#xff09;&#xff0c;该类负责创…

电商架构:系统设计+表设计

如有不对&#xff0c;请指正 欢迎评论区交流 需要哪些系统 商品系统、订单系统、权限系统、审核系统等。 商品系统 订单系统 审核系统 权限系统 参考 基于电商中台架构-商品系统设计(一) 附件

2024年【流动式起重机司机】报名考试及流动式起重机司机复审模拟考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年【流动式起重机司机】报名考试及流动式起重机司机复审模拟考试&#xff0c;包含流动式起重机司机报名考试答案和解析及流动式起重机司机复审模拟考试练习。安全生产模拟考试一点通结合国家流动式起重机司机考试…

电脑已经有了一个Windows10,再多装一个Windows10组成双系统

前言 前段时间已经讲过一次双Windows系统的安装教程&#xff0c;但是小白重新去看了一下&#xff0c;发现写的内容太多&#xff0c;怕小伙伴看了之后一脸萌。 所以今天咱们就重新再来讲讲&#xff1a;在同一台机器上安装Windows10双系统的教程。 注意哦&#xff01;这里的Wi…

Android优化RecyclerView图片展示:Glide成堆加载批量Bitmap在RecyclerView成片绘制Canvas,Kotlin(b)

Android优化RecyclerView图片展示&#xff1a;Glide成堆加载批量Bitmap在RecyclerView成片绘制Canvas&#xff0c;Kotlin&#xff08;b&#xff09; 对 Android GridLayoutManager Glide批量加载Bitmap绘制Canvas画在RecyclerView&#xff0c;Kotlin&#xff08;a&#xff09;-…

2024人工智能/机器学习/machine learning/CV/NLP重点公式汇总(算法面试考试论文)

### CV # Diffusion Model 扩散模型http://deepnlp.org/equation/diffusion-model-forward-processhttp://deepnlp.org/equation/diffusion-model-forward-process-reparameterizationhttp://deepnlp.org/equation/diffusion-model-reverse-processhttp://deepnlp.org/equation…

【GitHub】主页简历优化

【github主页】优化简历 写在最前面一、新建秘密仓库二、插件卡片配置1、仓库状态统计2、Most used languages&#xff08;GitHub 常用语言统计&#xff09;使用细则 3、Visitor Badge&#xff08;GitHub 访客徽章&#xff09;4、社交统计5、打字特效6、省略展示小猫 &#x1f…

求解约瑟夫问题

思路&#xff1a; 我们要创建两个指针 有一个指针pcur指向头结点&#xff0c;该pcur作为报数的指针&#xff0c;还有一个指针ptail指向尾结点&#xff0c;作为记录pcur的地址 每报数为m时&#xff0c;pcur指向下一个元素的地址&#xff0c;ptail销毁报数为m的地址&#xff0…

制糖工业智能工厂数字孪生可视化平台,推进制糖产业数字化转型

制糖工业智能工厂数字孪生可视化平台&#xff0c;推进制糖产业数字化转型。随着信息技术的快速发展&#xff0c;数字化转型已成为各行各业的重要趋势。在糖果加工制造领域&#xff0c;智能工厂数字孪生可视化平台的出现&#xff0c;为行业数字化转型注入了新的活力。 糖果加工制…

Java创建对象的最佳方式:单例模式(Singleton)

前言 单例模式是java中最简单的设计模式之一&#xff0c;属于创建式模式&#xff0c;提供了一种创建对象的最佳方式。 具体而言&#xff0c;单例模式涉及到一个具体的类&#xff0c;这个类可以确保只有单个对象被创建。它包含一个访问其唯一对象的方法&#xff0c;供外部直接…

算法训练营day25

零、回溯算法理论 参考链接13.1 回溯算法 - Hello 算法 (hello-algo.com) 1.尝试与回退 之所以称之为回溯算法&#xff0c;是因为该算法在搜索解空间时会采用“尝试”与“回退”的策略。当算法在搜索过程中遇到某个状态无法继续前进或无法得到满足条件的解时&#xff0c;它会…

python应用-socket网络编程(1)

目录 1 先简单回顾下客户端和服务端通信的知识 2 服务端常用函数 3 客户端常用函数 4 服务端和客户端都用的函数 5 示例介绍客户端和服务端通信过程 6 建立服务端套接制 7 创建服务端函数socket.create_server() 8 创建客户端套接字 9 客户端连接函数socket.create_co…