有哪些重构方法可以减少编译代码的大小?
我有一个需要新function的传统固件应用程序。 应用程序的大小已经接近设备的有限闪存容量,并且少数新function和变量将其推向了边缘。 打开编译器优化可以解决这个问题,但客户对这样做很谨慎,因为它们过去曾导致过失败。 那么,在重构C代码以产生更小的输出时,需要注意哪些常见的事情?
- 尽可能使用生成函数而不是数据表
- 禁用内联函数
- 将常用的宏转换为函数
- 降低大于本机大小的变量的分辨率(即8位微,尝试去除16和32位变量 – 一些代码序列的两倍和四倍)
- 如果micro具有较小的指令集(Arm thumb),则在编译器中启用它
- 如果内存是分段的(即分页或非线性)那么
- 重新排列代码,以便需要使用更少的全局调用(更大的调用指令)
- 重新排列代码和变量用法以消除全局内存调用
- 重新评估全局内存使用情况 – 如果它可以放在堆栈上那么好了
- 确保在关闭调试时进行编译 – 在某些处理器上它会产生很大的不同
- 压缩无法动态生成的数据 – 然后在启动时解压缩为ram以便快速访问
- 深入研究编译器选项 – 可能每个调用都是自动全局的,但您可以在逐个文件的基础上安全地禁用它以减小大小(有时显着)
如果您仍然需要更多空间而不是打开compile with optimizations
,那么请查看生成的程序集与未优化的代码。 然后重新编写发生最大变化的代码,以便编译器根据棘手的C重写生成相同的优化,并关闭优化。
例如,您可能有几个“if”语句进行类似的比较:
if(A && B && (C || D)){} if(A && !B && (C || D)){} if(!A && B && (C || D)){}
然后创建一个新变量并提前进行一些比较将使编译器免于复制代码:
E = (C || D); if(A && B && E){} if(A && !B && E){} if(!A && B && E){}
如果打开它,这是编译器为您自动执行的优化之一。 还有很多其他的,如果你想学习如何在C代码中手工完成这一点,你可以考虑阅读一些编译器理论。
通常:利用您的链接器映射或工具来确定最大/最多的符号是什么,然后可能使用反汇编程序查看它们。 你会发现这种方式会让你感到惊讶。
使用perl或类似程序,您可以缩短.xMAP文件或“objdump”或“nm”的结果,并以相应信息的各种方式对其进行重新排序。
特定于小指令集:观察文字池使用情况。 虽然从例如ARM(每条指令32位)指令集更改为THUMB(每条指令16位)指令集在某些ARM处理器上可能很有用,但它会减小“立即”字段的大小。
突然之间,从全局或静态直接加载的东西变得非常间接; 它必须首先将全局/静态的地址加载到寄存器中,然后从中加载,而不是直接在指令中编码地址。 因此,您可以在文字池中获得一些额外的指令和额外的条目,这些条目通常是一条指令。
解决这个问题的策略是将全局和静态组合成结构; 这样,您只存储一个文字(全局结构的地址)并从中计算偏移量,而不是在访问多个静态/全局变量时存储许多不同的文字。
我们将“单例”类从管理自己的实例指针转换为仅仅是大型“struct GlobalTable”中的成员,并且在某些情况下,它在代码大小(百分之几)和性能方面产生了明显的差异。
否则:留意静态结构和非平凡构造数据的数组。 这些中的每一个通常都会生成大量的.sinit代码(“隐藏函数”,如果你愿意的话),它们在main()之前运行,以正确填充这些数组。 如果你只能在静力学中使用琐碎的数据类型,那么你的状况会好得多。
这又是通过在“nm”或“objdump”等的结果上使用工具可以容易地识别的东西。 如果你有很多.sinit的东西,你会想要调查!
哦,并且 – 如果你的编译器/链接器支持它,不要害怕选择性地为某些文件或函数启用优化或更小的指令集!
重构掉重复的代码应该对程序的内存占用产生最大的影响。
注意宏。 他们可以从一次宏扩展中生成大量代码。 如果找到这样的宏 – 尝试重写它们,以便最小化它们的大小并将function转移到函数中。
注意重复的代码 – 复制粘贴和逻辑复制。 尝试将重复的代码分成function。
检查编译器是否支持内联,并且可以关闭它。
编译器优化会触发bug吗? 真奇怪。 获取程序的地图,看看是否应该定位数据或代码。 寻找重复的代码。 寻找具有类似目标的代码。 其中一个例子是busybox代码,旨在实现小内存占用。
它有利于大小而不是可读性,所以它有时会变得非常丑陋,有了getos等等。
上面的答案声称“打开编译器优化[减小代码大小]”。 鉴于我在嵌入式系统TI DSP编程方面的所有文档和经验,我知道开启优化将增加您的代码大小(对于TI DSP芯片)!
让我解释:
TI TMSCx6416 DSP有9个编译器标志,会影响您的代码大小。
- 优化的3个不同标志
- 调试的3个不同标志
- 代码大小有3种不同的标志
对于我的编译器,当您启用优化级别3时,文档说明:
- 将发生某些function的自动内联 – >将增加代码大小
- 软件流水线已启用 – >将增加代码大小
什么是软件流水线?
这就是编译器将在汇编中执行的操作,使for循环执行速度明显加快(速度提高几倍),但代价是代码更大。 我建议阅读维基百科上的软件流水线技术 (寻找循环展开,prolog和epilog)。
因此,请检查您的文档以确保优化不会使您的代码变大。
另一个建议是查找与代码大小相关的编译器标志。 如果您有代码大小编译器标志,请确保将它们调高到最高设置。 通常编译代码大小意味着您的代码将执行得更慢…但您可能必须这样做。