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

IDE:  CLion

HOST: Windows 11

MinGW:x86_64-14.2.0-release-posix-seh-ucrt-rt_v12-rev0

GCC: arm-gnu-toolchain-13.3.rel1-mingw-w64-i686-arm-none-eabi

一、简介

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

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

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

-O优化

一般有下面几个级别。当然上面还有-O4甚至-O5,不过个人感觉和-O3差不多.

  • -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);
}

————链接优化———— 

补充:

        改了一下CMakelists组织目录,接下来可以使用-flto优化,不过可能是由于资源限制或者哪里忘了配置,我使用这个优化选项,只能串行编译 9 个 LTRANS 任务,无法使用并行,所以链接时会比较慢。以此工程为例,编译用了6.32s左右,链接用了7.68s左右。

        下面将重复上面条件,在编译优化的基础上继续测试链接优化

-O0

        在不开编译优化的情况下,链接优化会使代码膨胀

         作为对比,下面是没有启用链接优化前

 -O1

        上图是链接优化后,下图是链接优化前,可以看到RAM略微上升,ROM小幅下降

性能为63.20ms,相较与编译优化的63.32ms,还是能看到些许优化效果的

-O2

        上图为链接优化,相比与下图,内存占用略微减少,ROM小幅提升

 

一帧所用为62.98ms,相较于先前的63.04ms,差不多

 -Og

内存分布上来看,RAM和FLASH都下降了

相较于先前的63.63ms,略微提升

-O3

相较于先前,ROM涨幅比较大,内存略微下降

相较于先前的63.03ms,此时的62.95ms略微提升

 -Ofast

        ROM小幅增加,RAM略微下降

        可以看到性能上与前面的-O3也差不多

 -Os

此时可以看到RAM略微下降,ROM小幅下降

        那么看看性能如何呢?结果很amazing啊,性能虽然连-O1都比不了,与没开链接优化前的63.63ms也差不多,但屏幕干净了许多,不怎么漏墨

 -Oz

虽比先前的占用略微减小,但与链接优化的-Os差不多

与上面的-Os差不多

三、结论 

        没有优化绘制算法前,不同优化级别差距还是比较明显的

        在经历了一系列手动优化后,我们可以看到除了-O0外,编译器(或连接器)优化对整体程序的性能优化并不怎么理想,对存储占用倒还算比较有用。换句话说,由于单片机上的计算资源一般并不怎么强,当软件手动优化到一定级别后,软件对整体性能的影响就不怎么大了,硬件的性能瓶颈对整体性能的制约占主要地位。

        事实上,以本次为例,开启优化仅能把性能从188ms提升到174ms。但如果优化算法、增加缓冲数组、开启硬件优化(使用DMA)等,却可以从174ms提升到前面的64ms左右。

        基于此,平时可以开-O3优化,不过要注意-O3下的优化很容易把一些变量优化掉,比如你随便定义了一个全局变量keyflag,然后在while循环里判断,那么很有可能会把它优化为寄存器,就导致即使接收到了按键信号,但寄存器也没有及时改变,进而无法正确及时处理按键任务。

        工程基本完善后,那么可以尝试-Oz,如果与-O3性能差不多,那么可以考虑使用-Oz,这样会进一步降低存储占用。工程确认无误后,可以开-flto进行最后的优化,进一步减少存储占用,并提高些许性能。

        

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

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

相关文章

C++之继承多态

C之继承&多态 继承继承之形继承的作用域继承的构造与析构多继承菱形继承 多态多态之形final和override(C11)纯虚函数&抽象类多态的原理打印虚表&#xff08;在vs2022中&#xff09;多继承下的虚表菱形虚继承中埋的坑静态多态与动态多态我对虚函数和普通成员函数调用区别…

机器学习-36-对ML的思考之机器学习研究的初衷及科学研究的期望

文章目录 1 机器学习最初的样子1.1 知识工程诞生(专家系统)1.2 知识工程高潮期1.3 专家系统的瓶颈(知识获取)1.4 机器学习研究的初衷2 科学研究对机器学习的期望2.1 面向科学研究的机器学习轮廓2.2 机器学习及其应用研讨会2.3 智能信息处理系列研讨会2.4 机器学习对科学研究的重…

arm 汇编技巧

汇编标号&#xff1a;f表示forward&#xff0c; b表示backward&#xff1a; Here is an example: 1: branch 1f 2: branch 1b 1: branch 2f 2: branch 1b Which is the equivalent of: label_1: branch label_3 label_2: branch label_1 label_3: branch label_4 label_4: bra…

特色3D打印stm32迷你8轴双核心主板

我自己设计的3D打印机主板 1. 这是一块迷你的8轴主板, 主板尺寸为100mm*75mm, 使用一个8cm静音风扇散热足够了2. 这是一个带有保护的板子, 驱动上的gpio具有过压保护功能, 能够直接抗住24V的冲击, 意味着一个驱动炸了, 板子不烧, 并且其他的驱动也没事, 主板支持自动关机3. 8…

golang分布式缓存项目 Day2 单机并发缓存

注&#xff1a;该项目原作者&#xff1a;https://geektutu.com/post/geecache-day1.html。本文旨在记录本人做该项目时的一些疑惑解答以及部分的测试样例以便于本人复习。 支持并发读写 接下来我们使用 sync.Mutex 封装 LRU 的几个方法&#xff0c;使之支持并发的读写。在这之…

2024 年将 Swagger 导入 Postman 图文教程

2024 年将 Swagger 导入 Postman 图文教程

从入门到精通:hello-algo开源项目助你系统学习数据结构与算法

文章目录 前言1.关于hello-algo2.安装Docker和Docker compose3.本地部署hello-algo4. hello-algo本地访问5.cpolar内网穿透工具安装6.创建远程连接公网地址7.固定Uptime Kuma公网地址 前言 本文将探讨如何在本地环境中部署hello-algo这一算法学习必备项目&#xff0c;并利用cp…

SystemVerilog学习笔记(七):函数与任务

函数 函数的主要用途是编写一段可以随时调用n次的代码&#xff0c;只需调用函数名即可&#xff0c;不需要任何模拟时间来执行。函数是返回类型&#xff0c;仅返回函数声明中提到的单个值&#xff0c;如果未声明则返回一个位的值。 语法&#xff1a; initial begin functio…

地下水数值模拟、 地下水环评、Visual modflow Flex、Modflow

地下水数值模拟软件Visual modflow Flex实践技术应用 地下水数值模拟软件的应用&#xff0c;主要围绕的目前应用较为广泛的Visual Modflow Flex 6.1软件版本开展&#xff0c;结合具体应用场景&#xff0c;实例讲解软件的全流程应用过程&#xff0c;包括数据处理分析、数值模型…

丹摩征文活动|Llama3.1:从安装到熟练使用的全方位教程

0.前言 目前关于 Llama 3.1 的详细安装和使用指南在网络上较为分散&#xff0c;对于许多想要深入了解和应用该模型的人来说&#xff0c;缺乏一个系统、全面的指导资料。为了填补这一空白&#xff0c;本文应运而生。旨在为广大读者提供从 Llama 3.1 的安装到熟练使用的全方位指…

UI自动化测试|CSS元素定位实践

前言 自动化测试元素定位是指在自动化测试过程中&#xff0c;通过特定的方法或策略来准确识别和定位页面上的元素&#xff0c;以便对这些元素进行进一步的操作或断言。这些元素可以是文本框、按钮、链接、图片等HTML页面上的任何可见或不可见的组件。 在自动化测试中&#xf…

软件架构与模式分析

软件架构模式分析 软件架构模式和架构风格是两个相关但不同的概念。 软件架构模式&#xff08;Software Architecture Patterns&#xff09;是一种在软件工程领域广泛应用的规范化、可复用的架构设计方案。它是通过抽象和提炼出解决特定问题所需的结构、组件、关系和规则等&am…

npm完整发包流程(亲测可验证)

1. 准备工作 &#xff08;1&#xff09; 在npm官网上注册一个账号 &#xff08;2&#xff09; 注册成功之后&#xff0c;npm会发送一封邮件给你&#xff0c;点击邮件里面的链接&#xff0c;做确认关联操作&#xff08;必需&#xff09; 2. 创建自己的npm包 &#xff08;…

无插件直播流媒体音视频播放器EasyPlayer.js播放器多分屏超过6路不能播放如何解决

EasyPlayer.js H5播放器&#xff0c;是一款能够同时支持HTTP、HTTP-FLV、HLS&#xff08;m3u8&#xff09;、WS、WEBRTC、FMP4视频直播与视频点播等多种协议&#xff0c;支持H.264、H.265、AAC、G711A、Mp3等多种音视频编码格式&#xff0c;支持MSE、WASM、WebCodec等多种解码方…

从零开始使用YOLOv11——Yolo检测detect数据集自建格式转换为模型训练格式:20w+图片1w+类别代码测试成功

在之前的文章中记录了YOLO环境的配置安装和基本命令的一些使用&#xff0c;上一篇博文的地址快速链接&#xff1a;从零开始使用YOLOv8——环境配置与极简指令&#xff08;CLI&#xff09;操作&#xff1a;1篇文章解决—直接使用&#xff1a;模型部署 and 自建数据集&#xff1a…

【HAProxy06】企业级反向代理HAProxy调度算法之其他算法

HAProxy 调度算法 HAProxy通过固定参数 balance 指明对后端服务器的调度算法&#xff0c;该参数可以配置在listen或backend选项中。 HAProxy的调度算法分为静态和动态调度算法&#xff0c;但是有些算法可以根据不同的参数实现静态和动态算法 相互转换。 官方文档&#xff1…

Leetcode 检测相邻递增子数组

3349. 检测相邻递增子数组 I 给你一个由 n 个整数组成的数组 nums &#xff0c;请你找出 k 的 最大值&#xff0c;使得存在 两个 相邻 且长度为 k 的 严格递增 子数组 。具体来说&#xff0c;需要检查是否存在从下标 a 和 b (a < b) 开始的 两个 子数组&#xff0c;并满…

【STL栈和队列】:高效数据结构的应用秘籍

前言&#xff1a; C 标准模板库&#xff08;STL&#xff09;为我们提供了多种容器&#xff0c;其中 stack&#xff08;栈&#xff09;和 queue&#xff08;队列&#xff09;是非常常用的两种容器。 根据之前C语言实现的栈和队列&#xff0c;&#xff08;如有遗忘&#xff0c;…

香江电器从A股到港股7年漫长上市路,收入后退停滞不前

《港湾商业观察》施子夫 9月29日&#xff0c;湖北香江电器股份有限公司&#xff08;以下简称&#xff0c;香江电器&#xff09;递表港交所引起外界关注&#xff0c;公司的独家保荐机构为国金证券。 回顾香江电器的IPO之旅&#xff0c;可以说是颇为坎坷&#xff0c;多次尝试A股…

从python源码到可自动更新软件

相关阅读 标题链接如何打包python程序为exebczl【auto-py-to-exe 可视化打包python到exe】51CTO ZATA 1. python源码 打包时需要特别注意的源码编写规范 除了基本的 Python 编码规范之外,在准备程序进行打包时,还需要特别注意以下几点: 1.1 依赖管理 确保 requirements.t…