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

news2024/11/19 23:40:03

在使用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.coloradmin.cn/o/1629248.html

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

相关文章

opencv4.8 系列一环境搭搭建

open 运行环境&#xff1a; vs2017 下载地址&#xff1a;https://www.123pan.com/s/cVyRVv-ydPWh.html 一&#xff1a;新建项目 二&#xff1a;核心代码&#xff1a; 在这里插入代码片 #include<opencv2/opencv.hpp>int main(int argc,char** argv) {cv::Mat src cv…

【软考高项】二十六、范围管理基础内容

一、管理基础 产品范围和项目范围 产品范围强调结果&#xff0c;项目范围强调结果 管理的新实践 &#xff1a;需求一直是项目管理的关注重点&#xff0c;需求管理过程结束于需求关闭&#xff0c;即把产品、服务或成果移交给接收方&#xff0c;以便长期测量、监控、实现并维持收…

ptyhon画图显示中文

import matplotlib.pyplot as plt import matplotlib# 设置中文字体 matplotlib.rcParams[font.sans-serif] [SimHei] matplotlib.rcParams[font.family]sans-serifplt.plot([1, 2, 3, 4]) plt.xlabel(这是x轴) plt.ylabel(这是y轴) plt.title(这是标题) plt.show()用这个代码…

anaconda安装python 3.8环境

打开anaconda命令行窗口 在命令行窗口中&#xff0c;输入命令&#xff1a;conda create -n py38 python3.8 执行命令后&#xff0c;显示conda版本、安装路径和安装的包 然后提醒是否安装&#xff0c;输入y 等待安装完成。然后进入python3.8&#xff0c;执行命令&#xff1a;con…

收藏:什么是协程的通俗解析

不错的视频&#xff1a;到底该怎么理解协程&#xff1f;_哔哩哔哩_bilibili 重点的要点&#xff1a; 比如这个函数&#xff1a; python中&#xff0c;使用yield关键字来做协程&#xff0c;就是暂停可以去执行其他东西&#xff0c;然后其他东西执行完后&#xff0c;继续执行yiel…

抓包理解协议

用的Wireshark 抓包 1.抓包网卡选择 - WLAN 无线网卡&#xff0c;其他是本地虚拟机的网卡 这里分别是开始捕获、停止捕获、重新捕获、网卡选择&#xff0c;下面是可以过滤选择 过滤tcp包 3次握手&#xff1a; source是源地址&#xff0c; destination是目标地址&#xff0c;in…

Mysql用语句创建表/插入列【示例】

一、 创建表 COMMENT表示字段或列的注释 -- 新建student表 CREATE TABLE student (id BIGINT NOT NULL COMMENT 学生id, enroll_date DATE NOT NULL COMMENT 注册时间, NAME VARCHAR(18) DEFAULT NOT NULL COMMENT 学生姓名, deal_flag TINYINT(1) DEFAULT 0 NOT NULL COMM…

创新入门|从点击到转化:AI个性化登陆页助力潜在客户转化

在数字营销的竞争格局中&#xff0c;采用先进技术对于旨在区分自己并吸引受众的企业至关重要。人工智能 &#xff08;AI&#xff09; 成为一项关键技术&#xff0c;尤其是在制作个性化登录页面的艺术方面。这些页面不仅仅是品牌与其潜在客户之间的第一个接触点;它们是吸引兴趣、…

vue-admin-template项目实现中英文切换

实现效果&#xff1a; 1.安装 *注意版本号 npm install vue-i18n8.24.5 -S2.新建文件夹 在src目录下新建lang文件夹&#xff0c;里面有3个文件 // index.js import Vue from vue import VueI18n from vue-i18n import Cookies from js-cookie import elementEnLocale fr…

Redis 服务等过期策略和内存淘汰策略解析

redis服务是基于内存运行的&#xff0c;所以很多数据都存放在内存中&#xff0c;但是内存又不是无限的&#xff0c;所以redis就引出了key的过期和淘汰策略。 一、Redis的过期策略&#xff1a; 我们在set key的时候&#xff0c;可以给它设置一个过期时间&#xff0c;比如expire …

【UnityRPG游戏制作】RPG项目的背包系统商城系统和BOSS大界面

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

【06】JAVASE-数组讲解【从零开始学JAVA】

Java零基础系列课程-JavaSE基础篇 Lecture&#xff1a;波哥 Java 是第一大编程语言和开发平台。它有助于企业降低成本、缩短开发周期、推动创新以及改善应用服务。如今全球有数百万开发人员运行着超过 51 亿个 Java 虚拟机&#xff0c;Java 仍是企业和开发人员的首选开发平台。…

华为MRS服务使用记录

背景&#xff1a;公司的业务需求是使用华为的这一套成品来进行开发&#xff0c;使用中发现&#xff0c;这个产品跟原生的Hadoop的那一套的使用&#xff0c;还是有很大的区别的&#xff0c;现记录一下&#xff0c;避免以后忘了 一、原始代码的下载 下载地址&#xff1a;MRS样例…

使用Umbrello学习工厂模式

工厂方法模式之所以有一个别名叫多态性工厂模式是因为具体工厂类都有共同的接口&#xff0c; 或者有共同的抽象父类。 当系统扩展需要添加新的产品对象时&#xff0c;仅仅需要添加一个具体对象以及一个具体工厂对 象&#xff0c;原有工厂对象不需要进行任何修改&#xff0c;也不…

Go语言中有哪些常见的编码规范和最佳实践?

文章目录 一、命名规范1. 包名2. 变量名3. 函数名原因 二、代码格式1. 花括号位置2. 缩进3. 空格示例代码原因 三、错误处理1. 检查错误2. 错误值3. 多重错误处理示例代码原因 四、注释1. 注释内容2. 注释位置3. 避免冗余注释示例代码原因 五、总结 在Go语言编程中&#xff0c;…

数据结构––复杂度

目录 一.时间复杂度 1.1定义 1.2时间复杂度的分类 1.3时间复杂度基本计算规则 1.4例子 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.4.10 1.4.11 1.4.12 1.4.13 二.空间复杂度 2.1定义 2.2推导大O阶方法 一.时间复杂度 1.1定义 算法的时间…

面试算法题精讲:最长公共子序列

面试算法题精讲&#xff1a;最长公共子序列 题面 题目来源&#xff1a;1143. 最长公共子序列 题目描述&#xff1a; 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长公共子序列&#xff08;LCS&#xff09;的长度。如果不存在公共子序列 &#xff0c;返回…

浅谈大数据时代下的电商风控||电商数据API接口

抢抢抢&#xff01;最后1天&#xff0c;双十一直播活动来啦&#xff01;抢直播专属优惠…… 视频号 随着大数据时代的兴起&#xff0c;互联网电商风控已经从无风控、人工抽取规则为主的简易规则模型发展到当前基于大数据的风控。与金融风控不同&#xff0c;互联网电商风控呈现出…

vue3 ——笔记 (条件渲染,列表渲染,事件处理)

条件渲染 v-if v-if 指令用于条件性地渲染一块内容&#xff0c;只有v-if的表达式返回值为真才会渲染 v-else v-else 为 v-if 添加一个 else 区块 v-else 必须在v-if或v-else-if后 v-else-if v-else-if 是v-if 的区块 可以连续多次重复使用 v-show 按条件显示元素 v-sh…

[Java]线程生命周期与线程通信

【版权声明】未经博主同意&#xff0c;谢绝转载&#xff01;&#xff08;请尊重原创&#xff0c;博主保留追究权&#xff09; https://blog.csdn.net/m0_69908381/article/details/138101131 出自【进步*于辰的博客】 线程生命周期与进程有诸多相似&#xff0c;所以我们很容易将…