有哪些重构方法可以减少编译代码的大小?

时间:2009-03-19 14:33:51

标签: c optimization memory embedded size

我有一个需要新功能的传统固件应用程序。应用程序的大小已经接近设备的有限闪存容量,并且少数新功能和变量将其推到了边缘。打开编译器优化可以解决这个问题,但客户对这样做很谨慎,因为它们过去曾导致过失败。那么,在重构C代码以产生较小的输出时,需要注意哪些常见的事情呢?

7 个答案:

答案 0 :(得分:20)

  • 尽可能使用生成函数代替数据表
  • 禁用内联函数
  • 将常用的宏转换为函数
  • 降低大于本机大小的变量的分辨率(即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代码中手工完成,你可以考虑阅读一些编译器理论。

答案 1 :(得分:8)

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

使用perl或类似的东西,你可以缩短.xMAP文件或“objdump”或“nm”的结果,并以各种方式对相关信息进行重新排序。


特定于小指令集:注意literal pool用法。从例如改变ARM(每条指令32位)指令设置为THUMB(每条指令16位)指令集在某些ARM处理器上非常有用,它减小了“立即”字段的大小。

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

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

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


否则:留意静态结构和非平凡构造数据的数组。这些中的每一个通常都会生成大量的.sinit代码(“隐藏函数”,如果你愿意的话),它们在main()之前运行,以正确填充这些数组。如果你只能在静力学中使用琐碎的数据类型,那么你的状况会好得多。

这又是通过在“nm”或“objdump”等结果上使用工具可以容易地识别的东西。如果你有很多.sinit的东西,你会想要调查!


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

答案 2 :(得分:2)

重构duplicate code应该对程序的内存占用产生最大的影响。

答案 3 :(得分:0)

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

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

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

答案 4 :(得分:0)

引发错误的编译器优化?真奇怪。 获取程序的地图,看看是否应该定位数据或代码。 寻找重复的代码。寻找具有类似目标的代码。其中一个例子是busybox代码,它的目标是占用大量内存。

它有利于大小而不是可读性,因此它有时会变得非常丑陋,有了等等。

答案 5 :(得分:0)

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


让我解释一下:

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

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

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

    1. 将发生某些功能的自动内联 - >将增加代码大小
    2. 启用软件流水线操作 - >将增加代码大小
    3. 什么是软件流水线?

      这就是编译器在汇编中做的事情,它使for循环的执行速度明显加快(速度提高了几倍),但代价是更大的代码。我建议阅读software pipelining at wikipedia(寻找循环展开,prolog和epilog)。

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


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

答案 6 :(得分:0)

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

1-请勿使用通用标准C库,例如 sprintf ,... 它们非常通用,如果您编写自己的函数,则会释放大量空间

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];
....
}