_浅谈单片机的gcc优化级别__以双音频信号发生器为例

 一、简介

        gcc有多种优化级别,一般不选择的情况下,IDE默认是按照-Og或这-O2优化的。

        以gcc编译器为例,浅谈一下优化级别,我们常见的优化一般是指gcc的-O2、-Og。除此之外,gcc还有-Os等一系列优化,链接器也有优化级别。

        基于单片机的开发,如果关注的是生成代码的大小,那么可以考虑-Os和-Oz。如果在乎性能的话,可以尝试-O2以上的优化级别

-O优化

一共有级别。当然上面还有-O4甚至-O5.

  • -O0:无优化,适合调试。
  • -O1:基本优化,适合快速迭代开发。
  • -O2:中级优化,适合大多数生产环境。
  • -O3:高级优化,适合对性能要求较高的应用。
  • -Ofast:极端优化,适合对性能要求极高且对标准合规性要求不高的应用。
  • -Og:调试优化,适合开发和调试阶段。
  • -Os:优化生成的代码大小,而不是性能。
  • -Oz:极度优化大小,进一步优化生成的代码大小,比 -Os更激进,可能会牺牲一些性能。
  • -flto:在链接阶段进行优化,允许进行跨文件的优化,进一步提高性能,但耗时间
  • -fprofile-generate(生成配置文件) 和 -fprofile-use(基于配置文件的优化)

从下面生成的代码体积来看优化对程序的影响

【-O0】

【-O1】

【-O2】

【-O3】

【-Ofast】

【-Og】

【-Os】

【-Oz】

-flto

        启用链接时优化,它会跨文件进行优化,会把所有东西混杂起来再优化,同时也会影响调试信息的生成。这就意味着,前面的-O级优化还能把编译出的二进制文件与你的源码对应起来,现在只能与反汇编对应。

        如下图,你甚至能看到CPU的那12个寄存器,此时展现在调试窗口的就是反汇编了。如果你想要让IDE调试程序时与源码对应,那么就需要加上-g编译标志

做个对比,虽然仅从代码量来观察是片面了许多,但多少能反映一些(原先代码忘记做速度测试了)。

上图是未开-flto,下图是开了-flto

【-O1】

【-O2】

【-O3】

【-Ofast】

【-Os】

【-Oz】

只不过有时候同样一份代码,用不同的方式优化可能还会报错,比如下面是-Og -flto优化,因为链接库的某种不知名原因

上述的-O级优化其实是由一系列单项优化组成的,可以组合,更适合竞赛宝宝体质的#pragram

-fwhole-program
目标:在整个程序范围内进行优化。
特点:
允许编译器在链接阶段对整个程序进行全局优化。
通常与 -O3 一起使用,以获得更高的性能。
-fprofile-generate 和 -fprofile-use
目标:基于运行时数据进行优化。
特点:
-fprofile-generate:生成配置文件。
-fprofile-use:使用生成的配置文件进行优化。
可以显著提高性能,特别是在热点路径上。
-fipa-cp-algorithm
目标:改进跨过程常量传播算法。
特点:
用于改进跨过程的常量传播,提高代码性能。
-fipa-pta
目标:改进指针分析。
特点:
用于改进指针分析,提高代码性能。 
-funroll-loops
目标:展开循环。
特点:通过展开循环减少循环开销,提高性能。
-finline-functions
目标:内联函数。
特点:自动内联小函数,减少函数调用开销。
-fomit-frame-pointer
目标:省略帧指针。
特点:在函数调用中省略帧指针,减少寄存器使用,提高性能。
-fstrict-aliasing
目标:启用严格的别名规则。
特点:允许编译器进行更激进的优化,假设不同类型的指针不会指向同一内存地址。
-ftree-vectorize
目标:启用向量化优化。
特点:将循环中的标量操作转换为向量操作,利用 SIMD 指令集提高性能。
-floop-interchange
目标:交换循环顺序。
特点:优化嵌套循环的顺序,提高缓存利用率。
-floop-strip-mine
目标:分割循环。
特点:将大循环分割成多个小循环,提高缓存利用率。
-floop-block
目标:块划分循环。
特点:将循环体划分为多个块,提高缓存利用率。
-fgraphite
目标:启用 Graphite 循环变换框架。
特点:使用高级循环变换技术优化循环性能。
-fipa-sra
目标:启用跨过程结构体拆解。
特点:在跨过程调用中拆解结构体,减少内存访问开销。

链接器也有一系列优化,就是不常用,包括上面提到的一系列组合,对于单片机开发来说

-Wl,--hash-style=both
目标:使用两种哈希风格。
特点:链接器使用两种哈希风格(SYSV 和 GNU),提高符号查找效率。
-Wl,--no-undefined
目标:禁止未定义的符号。
特点:链接器在链接时检查未定义的符号,确保所有符号都已定义。
-Wl,--no-merge-exidx-entries
目标:禁止合并异常索引条目。
特点:防止链接器合并异常索引条目,确保异常处理的准确性。
-Wl,--sort-common
目标:按大小排序公共符号。
特点:链接器按大小排序公共符号,提高内存布局的效率。
-Wl,--sort-section=name
目标:按名称排序节区。
特点:链接器按名称排序节区,提高内存布局的效率。
-Wl,--no-keep-memory
目标:释放内存。
特点:链接器在链接过程中释放不再需要的内存,减少内存占用。

 二、测试

        这里做了一点点简单不那么严谨的小测试,使用的测试工程为下面链接中的双音频信号发生器ichliebedich-DaCapo/STM32F407VET6: stm32f407vet6 (github.com)

-O0:

       结果很感人,烧录时一切正常

        但按下按键后还没怎么执行就卡住了。

        在卡住之后,我们停下来可以清楚地看到堆栈爆了(栈区溢出,下方蓝色的msp寄存区),直接的影响就是LVGL处理事件时,访问数组直接越界。换句话说,如果下次碰到了LVGL数组越界,那么就要怀疑是栈区溢出了。

现在看一看编译大小

        -gc-sections是去除不用的段,--print-memory-usage是打印内存分布,Map=${BIN_DIR}/${PROJECT_NAME}.map是生成map映射文件。当然,前面都得有-Wl

add_link_options(-Wl,-gc-sections,--print-memory-usage,-Map=${BIN_DIR}/${PROJECT_NAME}.map)

-O1:

        同样的代码,使用-O1可以很明显地看到优化情况

        下面将以以内置的FPS组件显示,在128个数据点、线性插值算法、800Hz(只是虚拟的,不是真的)下进行测试

        FPS组件代码是基于LVGL的文本框组件写的一个小类,没有做什么性能上的优化,但简单测试衡量一下性能变化还是可以做到的。


/*** @brief 工具类*/
class Tools
{
public:static inline auto fps_init(Font font, Coord x = 0, Coord y = 40, Coord width = 60, Coord height = 20) -> void;// 显示fpsstatic inline auto fps(bool time = true) -> void;static inline auto restart_fps() -> void;static inline auto set_right() -> void;static inline auto set_left() -> void;static inline auto set_center() -> void;static inline auto clear_fps() -> void;private:// 获取时间static inline auto get_tick() -> uint32_t;// 单线程更新事件static inline auto update_tick() -> void;private:static inline Obj_t label_fps{};static inline uint32_t count = 0;static inline uint32_t tick = 0;
};/*** @brief fps功能初始化* @param font 指定字库中要有fps和十位数字,字体大小为13即可* @param x x轴* @param y y轴* @note 默认文本框为60*20,即宽60,高20,且文本为左对齐。*/
auto Tools::fps_init(Font font, Coord x, Coord y, Coord width, Coord height) -> void
{Text label;label.init_font(font);
#if SIMPLE_FPSlabel.init(label_fps, x, y, width, height, "");
#elselabel.init(label_fps, x, y, 60, 80, "fps\n0");
#endif}/*** @brief fps显示* @note 启用该功能之前必须先调用fps_init进行必要的初始化。启用fps显示,即在需要的地方调用本函数*      默认显示一帧需要的时间单位为ms*/
auto Tools::fps(bool time) -> void
{
#if SIMPLE_FPSchar buf[7];if (time){// 显示一帧的时间sprintf(buf, "%.2fms", 1.0 * get_tick() / (count++));} else{// 显示帧率sprintf(buf, "%.2f", 1000.0 * (count++) / get_tick());}Text::set_text(buf, label_fps);
#elsechar buf[9];// 显示一帧的时间sprintf(buf, "fps\n%.2f", 1.0*get_tick() / (count++));// 显示帧率
//    sprintf(buf, "fps\n%.2f", 1000.0*(count++))/get_tick();Text::set_text(buf, label_fps);
#endif
}auto Tools::get_tick() -> uint32_t
{uint32_t temp_tick = lv_tick_get();// 防止溢出if (temp_tick < tick)temp_tick += (0xFFFF'FFFF - tick);elsetemp_tick -= tick;return temp_tick;
}auto Tools::update_tick() -> void
{tick = lv_tick_get();
}/*** @brief 重启fps*/
auto Tools::restart_fps() -> void
{update_tick();count = 0;
}auto Tools::set_right() -> void
{Text::set_text_align(LV_TEXT_ALIGN_RIGHT, label_fps);
}auto Tools::set_center() -> void
{Text::set_text_align(LV_TEXT_ALIGN_CENTER, label_fps);
}auto Tools::set_left() -> void
{Text::set_text_align(LV_TEXT_ALIGN_LEFT, label_fps);
}auto Tools::clear_fps() -> void
{Text::set_text("", label_fps);}

可以看出,一帧所用时间为36.88ms左右,并且屏幕右侧有严重的漏墨现象,这是由于绘制像素点函数LCD_Set_Pixel不够卖力(主频不够高)导致的

-O2:

        接下来使用-O2级别,同上面相比,我们可以看到RAM和Flash都略微增加了少许

 

        接下来测试一下性能,从右上角的36.79ms可以看出,相比-O1可能有了那么一点点提升(因为不能排除误差),从观察效果来看,漏墨现象也是挺严重的。在我印象中,应该比-O1强一些才对,可能是这次没发挥好(不同工程、相同的优化级别,显现的效果是不同的)

 -Og:

        接下来我们看看平时最常用的调试级别优化能拿出怎样的成绩吧。首先是代码体积比-O1还小了一点,内存占用相同。

        接下来看看性能,37.16ms,可能是由于调试信息的原因性能就略逊一筹,不过与-O1、-O2也大差不差

-O3:

        接下来有请-O3大佬, 一出手就是非同凡响,RAM占用些许提升,ROM大幅提升

从性能上来看,竟然与前面差不多,那么可以说明一个问题,现在性能的瓶颈不在于算法,而在于打点速度。真是失策,测了这么多有种白费的感觉。

 ----------------

        不过接下来换种算法测试一下,就以样条算法为例,这个与贝塞尔曲线差不多的速度,比线性插值慢,但稳定多了,帧率最后会趋于一个稳定的值,所以测试结果相对要可靠一些。

        由于工程不变,所以就不继续展示代码大小了

 -O1

-O2

-Og

-O3

-Ofast

代码体积比-O3多了一点点,-O3 -ffast-math、-O4、-O5在代码体积上与-O3完全一致

该优化被clang淘汰掉了,取而代之的是-O3 -ffast-math。gcc还有是-Ofast的

-Os

代码确实小了一些

看看性能,63.63ms与-Og差不多

-Oz

看来代码的体积已经被压缩到极致了

性能与-Os差不多,但与-Ofast比起来就相对明显

         至于-flto优化这个我现在无法测试,因为改了文件组织编译方式,把大部分文件都分别编译为静态库,然后再统一链接成elf文件。所以无法使用-flto,一使用就会出现找不到定义的错误。之前没改CMakelists前,使用-flto,代码体积上确有优化,但性能没有测试过。

    add_compile_options(-flto )

        -O3及以上的优化要慎重对待,上次基于样条算法编写一个模板函数,只有在-O2下可以正常运行,开-O3以上 会卡死,开-O2以下堆栈会爆,真是让人摸不到头脑。后来也不知改了什么,或许是改动了其他函数间接导致这个模板函数又行了,在-O1到-Ofast均可正常使用。

        编译器优化,很玄

         另提一嘴,在使用总大小相同的缓冲数组情况下,LVGL的双缓冲要优于单缓冲。设置双缓冲也很简单,只有在旁边另加一个静态数组,然后把数组名填入到lv_disp_draw_buf_init的第三个参数中

/*** @brief 初始化显示驱动* @tparam flush 涂色函数,有LCD驱动提供* @note 为了让lambda表达式可以不用捕获外部函数,只能使用函数模板。如果使用函数指针来传递就必须要显示捕获*/
template<void (*flush)(uint16_t, uint16_t, uint16_t, uint16_t, const uint16_t *)>
auto GUI::disp_drv_init() -> void
{// 在缓冲数组总大小同等的情况下,双缓冲明显优于单缓冲static lv_disp_draw_buf_t draw_buf_dsc;static lv_color_t buf_2_1[MY_DISP_HOR_RES * MY_DISP_BUF_SIZE];static lv_color_t buf_2_2[MY_DISP_HOR_RES * MY_DISP_BUF_SIZE];lv_disp_draw_buf_init(&draw_buf_dsc, buf_2_1, buf_2_2,MY_DISP_HOR_RES * MY_DISP_BUF_SIZE);   /*Initialize the display buffer*/lv_disp_drv_init(&disp_drv);                    /*Basic initialization*/disp_drv.hor_res = MY_DISP_HOR_RES;disp_drv.ver_res = MY_DISP_VER_RES;
// C环境下就不要使用lambda表达式,自行定义flush函数disp_drv.flush_cb = [](lv_disp_drv_t *, const lv_area_t *area, lv_color_t *color_p){flush(area->x1, area->y1, area->x2, area->y2, (const uint16_t *) color_p);};disp_drv.draw_buf = &draw_buf_dsc;lv_disp_drv_register(&disp_drv);
}

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

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

相关文章

qt QTreeWidgetItem详解

1、概述 QTreeWidgetItem 是 Qt 框架中的一个类&#xff0c;专门用于在 QTreeWidget&#xff08;一个基于项的树形视图&#xff09;中表示单个节点&#xff08;或称为项&#xff09;。QTreeWidget 继承自 QAbstractItemView&#xff0c;而 QTreeWidgetItem 则作为树中的一个节…

[每周一更]-(第122期):模拟面试|数据库面试思路解析

10|数据库索引:为什么 MySQL 用 B+ 树而不用 B 树? 为什么 MySQL 用 B+ 树而不用 B 树? 什么是覆盖索引? 什么是聚簇索引/非聚簇索引? 什么是哈希索引?MySQL InnoDB 引擎怎么创建一个哈希索引? 什么回表?如何避免回表? 树的高度和查询性能是什么关系? 什么是索引最左…

java-智能识别车牌号_基于spring ai和开源国产大模型_qwen vl

用大模型做车牌号识别&#xff0c;最简单高效 在Java场景中&#xff0c;java识别车牌号的需求非常普遍。过去&#xff0c;我们主要依赖OCR等传统方法来实现java识别车牌号&#xff0c;但这些方法的效果往往不稳定。随着技术的发展&#xff0c;现在有了更先进的解决方案——大模…

【学习笔记】SAP ABAP——子程序

子程序&#xff1a; 参数&#xff1a; 参数(Parameter)是指调用子程序时用于传入、传出的值。子程序中的参数与一般用 DATA语句定义的局部变量相同。调用子程序时使用的参数叫实参(Actual Parameter)&#xff0c;在子程序中使用的参数叫虚参(Formal Parameter)。PERFORM 利用…

计算机专业开题报告写法,该怎么写好?

不会写开题报告&#xff0c;或者想要一些论文模版的&#xff0c;欢迎评论&#xff0c;会第一时间给大家。 题报告是计算机专业大学毕业生在开展毕业设计或论文研究前&#xff0c;对研究课题进行详细介绍和计划的重要环节。作为开题者对科研课题的一种文字说明&#xff0c;开题…

Python世界:力扣题704二分查找

Python世界&#xff1a;力扣题704二分查找 任务背景思路分析代码实现测试套件本文小结 任务背景 问题来自力扣题目704&#xff1a;Binary Search&#xff0c;大意如下&#xff1a; Given an array of integers nums which is sorted in ascending order, and an integer target…

FreeRTOS学习日志--中断测试实验,以及遇到的问题

目录 实验项目&#xff1a;FreeRTOS 中断测试实验 1、实验目的 2、实验设计 遇到的问题 stm32F103战舰在运行程序后&#xff0c;USB232串口不能接收到信号问题。 从跑马灯FreeRTOS文件基础上移植的FreeRTOS中断无效&#xff0c;而例程中断有效问题。 问题来源与解决方法…

shodan[3](泷羽sec)

声明 学习视频来自B站UP主 泷羽sec,如涉及侵泷羽sec权马上删除文章。 笔记只是方便各位师傅学习知识,以下网站只涉及学习内容,其他的都与本人无关,切莫逾越法律红线,否则后果自负 这节课旨在扩大自己在网络安全方面的知识面&#xff0c;了解网络安全领域的见闻&#xff0c;了…

Windows系统服务器怎么设置远程连接?详细步骤

一、什么是Windows远程桌面连接? Windows远程桌面(Remote Desktop)功能使用户能够通过网络连接到另一台Windows计算机&#xff0c;实现远程操作。远程桌面非常适合系统管理员、技术支持人员以及那些需要远程工作的人&#xff0c;它允许用户以图形界面的方式访问远程计算机&…

Spring配置文件初始化加载(一)

1.枚举 public enum TestEnum {type_01("01", "zeroTest01ServiceImpl"),type_02("02", "zeroTest02ServiceImpl"),type_03("03", "zeroTest03ServiceImpl");private String type;private String pathClass; } …

Element UI组件Dialog显示闪动问题【解决方案】

在ElementUI中&#xff0c;el-dialog弹窗确实有时会导致页面出现抖动或闪动的问题。这通常是由于弹窗出现时对页面布局的影响&#xff0c;特别是滚动条的出现或消失&#xff0c;导致了页面的重新布局和渲染。以下是一些解决或缓解这一问题的方法&#xff1a; 解决方案 1. 关闭…

【022C】基于51单片机音乐盒

☆、设计硬件组成&#xff1a;51单片机最小系统按键控制LCD1602液晶显示DS18B20温度传感器喇叭。 1、本设计采用STC89C51/52、AT89C51/52、AT89S51/52作为主控芯片&#xff1b; 2、采用LCD1602显示播放歌曲名、歌曲序号、歌曲播放时间&#xff1b; 3、播放音乐通过按键可以实…

计算机毕业设计Python流量检测可视化 DDos攻击流量检测与可视化分析 SDN web渗透测试系统 网络安全 信息安全 大数据毕业设计

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

BO-CNN-LSTM回归预测 | MATLAB实现BO-CNN-LSTM贝叶斯优化卷积神经网络-长短期记忆网络多输入单输出回归预测

BO-CNN-LSTM回归预测 | MATLAB实现BO-CNN-LSTM贝叶斯优化卷积神经网络-长短期记忆网络多输入单输出回归预测 目录 BO-CNN-LSTM回归预测 | MATLAB实现BO-CNN-LSTM贝叶斯优化卷积神经网络-长短期记忆网络多输入单输出回归预测效果一览基本介绍模型搭建程序设计参考资料 效果一览 …

基于统计方法的语言模型

基于统计方法的语言模型 基于统计方法的语言模型主要是指利用统计学原理和方法来构建的语言模型&#xff0c;这类模型通过分析和学习大量语料库中的语言数据&#xff0c;来预测词、短语或句子出现的概率。 N-gram模型&#xff1a;这是最基础的统计语言模型之一&#xff0c;它基…

基于Python+Vue开发的蛋糕商城管理系统

项目简介 该项目是基于PythonVue开发的蛋糕商城管理系统&#xff08;前后端分离&#xff09;&#xff0c;这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Python编程技能&#xff0c;同时锻炼他们的项目设计与开发能力。通过学习基于Python的蛋糕商…

【node模块】深入解读node:assert模块

&#x1f9d1;‍&#x1f4bc; 一名茫茫大海中沉浮的小小程序员&#x1f36c; &#x1f449; 你的一键四连 (关注 点赞收藏评论)是我更新的最大动力❤️&#xff01; &#x1f4d1; 目录 &#x1f53d; 前言1️⃣ 什么是node:assert模块&#xff1f;2️⃣ node:assert模块的核心…

JDBC学习记录

文章目录 一、JDBC简介1.1、 JDBC概念1.2、 JDBC本质1.3、 JDBC好处 二、JDBC快速入门2.1、 编写代码步骤2.2、 代码示例 三、JDBC API详解3.1、DriverManager3.1.1、注册驱动3.1.2、获取连接 3.2、Connection3.2.1、获取执行对象3.2.2、事务管理 3.3、Statement3.3.1、执行DDL…

【物联网技术】ESP8266 WIFI模块在STA模式下实现UDP与电脑/手机网络助手通信——UDP数据透传

前言:完成ESP8266 WIFI模块在STA模式下实现UDP与电脑/手机网络助手通信——实现UDP数据透传 STA模式,通俗来说就是模块/单片机去连接路由器/热点来通信。 UDP协议,是传输层协议,UDP没有服务器和客户端的说法。 本实验需要注意,wifi模块/单片机与电脑/手机需要连接在同一个…

Linux和,FreeRTOS 任务调度原理,r0-r15寄存器,以及移植freertos(一)

目录、 1、r0-r15寄存器&#xff0c;保护现场&#xff0c;任务切换的原理 2、freertos移植 3、freertos的任务管理。 一、前言 写这篇文章的目的&#xff0c;是之前面试官&#xff0c;刚好问到我&#xff0c;移植FreeRTOS 到mcu&#xff0c;需要做哪些步骤&#xff0c;当时回…