16

我有一个需要新功能的旧版固件应用程序。应用程序的大小已经接近设备有限的闪存容量,少数新功能和变量将其推到了边缘。打开编译器优化可以解决问题,但客户对此持谨慎态度,因为他们过去曾导致失败。那么,在重构 C 代码以产生更小的输出时,需要寻找哪些常见的东西呢?

4

7 回答 7

23
  • 尽可能使用生成函数而不是数据表
  • 禁用内联函数
  • 把常用的宏变成函数
  • 降低大于本机机器大小的变量的分辨率(即 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 代码中手动执行此操作,您可能会考虑阅读一些编译器理论。

于 2009-03-19T14:38:00.633 回答
8

通常:使用您的链接器映射或工具来确定您最大/最多的符号是什么,然后可能使用反汇编程序查看它们。你会惊讶于你以这种方式找到的东西。

使用一些 perl 等,您可以对 .xMAP 文件或“objdump”或“nm”的结果进行简短处理,并以各种方式重新排序以获取相关信息。


特定于小型指令集:注意文字池的使用。虽然从例如 ARM(每条指令 32 位)指令集更改为 THUMB(每条指令 16 位)指令集在某些 ARM 处理器上可能很有用,但它减小了“立即”字段的大小。

突然之间,来自全局或静态的直接负载变得非常间接;它必须首先将全局/静态的地址加载到寄存器中,然后从中加载,而不是直接在指令中编码地址。因此,您会在文字池中获得一些额外的指令一个额外的条目,这些条目通常是一条指令。

解决这个问题的策略是将全局变量和静态变量组合成结构;这样,您只存储一个文字(全局结构的地址)并从中计算偏移量,而不是在访问多个静态/全局变量时存储许多不同的文字。

我们将“单例”类从管理它们自己的实例指针转换为仅作为大型“struct GlobalTable”中的成员,并且在某些情况下,它在代码大小(几个百分比)和性能方面产生了显着差异。


否则:留意静态结构和非平凡构造的数据数组。其中每一个通常会生成大量 .sinit 代码(“不可见的函数”,如果您愿意的话),这些代码在 main() 之前运行以正确填充这些数组。如果您只能在静态数据中使用微不足道的数据类型,那么您的情况会好得多。

这又是可以通过使用工具对“nm”或“objdump”等的结果轻松识别的东西。如果您有大量 .sinit 文件,您会想要进行调查!


哦,而且——如果你的编译器/链接器支持它,不要害怕为某些文件或函数选择性地启用优化或更小的指令集!

于 2009-03-19T15:57:29.840 回答
2

重构重复代码应该对程序的内存占用影响最大。

于 2009-03-19T14:37:26.203 回答
0

注意宏。他们可以从一个宏扩展中生成大量代码。如果您发现这样的宏 - 尝试重写它们,以最小化它们的大小并将功能转移到函数中。

注意重复的代码 - 复制粘贴和逻辑重复。尝试将重复的代码分成函数。

检查编译器是否支持内联,可以关闭。

于 2009-03-19T14:39:48.990 回答
0

触发错误的编译器优化?那很奇怪。获取您的程序的地图,看看您是否应该以数据或代码为目标。寻找重复的代码。寻找具有类似目标的代码。其中一个示例是busybox 代码,它旨在减少内存占用。

它更倾向于大小而不是可读性,所以有时它会变得非常丑陋,比如 goto 等等。

于 2009-03-19T14:43:58.357 回答
0

上述答案声称“打开编译器优化[减少代码大小]”。鉴于我在嵌入式系统TI DSP 编程方面的所有文档和经验,我知道启用优化将增加您的代码大小(对于 TI DSP 芯片)!


让我解释:

TI TMSCx6416 DSP 有 9 个编译器标志,它们会影响您的代码大小。

  1. 3个不同的优化标志
  2. 3 个不同的调试标志
  3. 代码大小的 3 个不同标志

对于我的编译器,当您打开优化级别 3 时,文档说明:

  1. 将发生某些函数的自动内联 --> 将增加代码大小
  2. 启用软件流水线 --> 将增加代码大小

什么是软件流水线?

这就是编译器将在汇编中做的事情,使 for 循环执行得更快(最多快几倍),但以更大的代码大小为代价。我建议阅读维基百科上的软件流水线(寻找循环展开、序言和结语)。

因此,请检查您的文档以确保优化不会使您的代码变大。


另一个建议是寻找与代码大小相关的编译器标志。如果您有代码大小编译器标志,请确保将它们提高到最高设置。通常针对代码大小进行编译意味着您的代码执行速度会变慢……但您可能必须这样做。

于 2009-03-19T17:44:54.357 回答
0

你可以做很多事情,但是这两件事过去对我帮助很大,我只想建议一个

1-不要使用像sprintf这样的通用标准 C 库,...它们非常通用,如果您编写自己的函数,它会释放大量空间

2-如果你有一个 char 数组的本地声明,如果你知道最大长度,你应该明确给出长度,而不是通过输入参数获取长度,例如

如果你有这样的功能

void foo(char* str,uint8_t length){
char local_string[length];
....
}

您最好找到您使用的最大长度,然后将其更改为

void foo(char* str,uint8_t length){
char local_string[MAXIMUM_LENGTH];
....
}
于 2019-04-09T23:04:47.817 回答