如何压缩代码以获得更多闪存和RAM?[关闭]


14

我一直在为我们的特定产品开发功能。有人要求将相同功能移植到另一产品。该产品基于M16C微控制器,该微控制器传统上具有64K闪存和2k RAM。

它是一种成熟的产品,因此,只剩下132字节的闪存和2字节的RAM。

要移植请求的功能(功能本身已经过优化),我需要1400字节的Flash和〜200字节的RAM。

有没有人对如何通过代码压缩检索这些字节有任何建议?当我试图压缩已经存在的工作代码时,我需要寻找什么特定的东西?

任何想法将不胜感激。

谢谢。


1
谢谢大家的建议。我将为您提供最新进展并列出有效的步骤和无效的步骤。
IntelliChick 2010年

好的,这是我尝试过的工作:提升编译器版本。优化得到了极大的改善,使我获得了大约2K的Flash。浏览列表文件以检查特定产品的冗余和未使用功能(由于通用代码库而被继承),并获得了更多的Flash。
IntelliChick 2010年

对于RAM,我执行了以下操作:浏览映射文件,以检查使用最多RAM的功能/模块。我发现了一个非常繁重的功能(12个通道,每个通道都有固定数量的已分配内存),它可以通过共享常用通道之间的信息来了解其试图实现的目标,并优化了RAM的使用。这给了我约200个字节。
IntelliChick 2010年

如果您有ascii文件,则可以使用8到7位压缩。为您节省12.5%。使用zip文件进行压缩和解压缩将花费更多的代码,而不仅仅是这样做。
Sparky256 '18

Answers:


18

您有两种选择:首先是查找冗余代码,然后将其移至单个调用中以消除重复。第二个是删除功能。

仔细查看.map文件,看看是否有一些可以删除或重写的函数。还要确保确实需要使用正在使用的库调用。

诸如除法和乘法之类的某些事情可以带来很多代码,但是使用移位和更好地使用常量可以使代码更小。还可以看一下字符串常量和printfs之类的东西。例如,每个printf都会占用您的rom,但您可能可以拥有几个共享格式的字符串,而不必一次又一次地重复该字符串常量。

对于内存,请查看是否可以摆脱全局变量,而在函数中使用自动变量。还要避免在主函数中使用尽可能多的变量,因为它们就像全局变量一样会消耗内存。


1
感谢您的建议,我肯定可以尝试其中的大多数建议,但字符串常量除外。它纯粹是嵌入式设备,没有UI,因此代码内没有对printf()的调用。希望这些建议能帮我找到所需的1400字节Flash / 200字节RAM。
IntelliChick 2010年

1
@IntelliChick您会惊讶于有多少人使用嵌入式设备内部的printf()进行打印以进行调试或发送到外围设备。似乎您对此有所了解,但是如果有人在您之前在项目上编写过代码,则对其进行检查不会有任何伤害。
Kellenjb 2010年

5
只是为了扩展我之前的评论,您还会为有多少人添加调试语句而从未删除它们感到惊讶。即使执行#ifdefs的人有时也会变得懒惰。
凯伦杰(Kellenjb)2010年

1
很好,谢谢!我已经继承了此代码库,因此请务必对这些代码有所了解。我会随时向大家发布进度,并尝试跟踪我通过执行操作获得的内存或闪存字节数,以作为将来可能需要这样做的其他人员的参考。
IntelliChick 2010年

这只是一个问题-嵌套函数调用层与层之间的跃迁如何?这会增加多少开销?通过具有多个函数调用来保持模块化或减少函数调用并节省一些字节,是否更好?那有意义吗?
IntelliChick 2010年

8

始终值得一看清单文件(汇编程序)输出,以查找您的特定编译器特别擅长的事情。

例如,您可能会发现局部变量非常昂贵,并且如果应用程序足够简单就值得承担风险,那么将一些循环计数器移入静态变量可能会节省大量代码。

或数组索引可能非常昂贵,但指针操作便宜得多。或相反亦然。

但是看汇编语言是第一步。


3
了解编译器的功能非常重要。您应该看到我的编译器存在什么鸿沟。它使婴儿哭泣(包括我自己)。
Kortuk

8

例如,-OsGCC中的编译器优化可在速度和代码大小之间实现最佳平衡。避免使用-O3,因为它可能会增加代码大小。


3
如果这样做,则将需要重新测试所有内容!由于编译器做出了新的假设,优化可能导致工作代码无法正常工作。
罗伯特2010年

@Robert,仅当您使用未定义的语句时,这才是正确的:例如,a = a ++将在-O0和-O3中进行不同的编译。
Thomas O

5
@托马斯不是真的。如果您有一个for循环来延迟时钟周期,那么许多优化器将意识到您不执行任何操作并将其删除。这只是一个例子。
Kellenjb 2010年

1
@thomas O,您还需要确保对易失函数定义小心。优化器将炸毁那些认为自己很了解C但不了解原子操作复杂性的函数。
Kortuk

1
所有的优点。根据定义,易失性函数/变量不得进行优化。对此进行优化的任何优化器(包括调用时间和内联)都将被破坏。
Thomas O

8

对于RAM,请检查所有变量的范围-是否在可以使用char的位置使用int?缓冲区是否大于所需的大小?

代码压缩非常依赖于应用程序和编码样式。您剩下的金额表明,也许代码已经经过了一些压缩,但这意味着几乎没有剩余了。

还仔细看一下整体功能-是否有未真正使用且可以抛弃的东西?


8

如果这是一个旧项目,但是编译器是从那时开始开发的,则可能是较新的编译器可能会生成较小的代码


谢谢迈克!我过去曾尝试过此方法,但获得的空间已被使用!:)从IAR C编译器3.21d移至3.40。
IntelliChick 2010年

1
我又升级了一个版本,并设法获得了更多的Flash以适应该功能。尽管如此,我确实在RAM方面苦苦挣扎,但仍保持不变。:(
IntelliChick 2010年

7

始终值得检查编译器手册中提供的选项以优化空间。

对于gcc -ffunction-sections-fdata-sections带有--gc-sections链接器标志非常适合剥离死代码。

以下是一些其他出色的技巧(针对AVR)


这真的有效吗? 文档说:“当您指定这些选项时,汇编器和链接器将创建较大的对象和可执行文件,并且速度也较慢。” 我知道,对于具有Flash和RAM部分的微型计算机,有单独的部分是有意义的-文档中的此声明不适用于微控制器吗?
凯文·维米尔

我的经验是,它对AVR效果很好
Toby Jaffey 2010年

1
在我使用过的大多数编译器中,这不能很好地工作。就像使用register关键字一样。您可以告诉编译器变量进入寄存器,但是好的优化器会比人工优化(在某些情况下可能会争论,在实践中认为这样做是不可接受的)。
Kortuk

1
当您开始分配位置时,您将迫使编译器将它们放置在某些位置,这对于高级引导加载程序代码非常重要,但是在优化器中难以处理,因为您为此做出决策时就放弃了对其进行优化的步骤能做。在某些编译器中,他们将其设计为包含用于代码用途的部分,这种情况是告诉编译器更多信息以了解您的用法,这将有所帮助。如果编译器不建议这样做,请不要这样做。
Kortuk

6

您可以检查分配的堆栈空间和堆空间量。如果其中一个或两个都被过度分配,则可能能够获得大量的RAM。

我的猜测是对一个项目适合进入的RAM 2K下手没有动态内存分配(使用的malloccalloc等等)。如果是这种情况,则可以假定原始作者为堆分配了一些RAM,从而完全摆脱了堆。

您必须非常小心地减小堆栈大小,因为这会导致很难发现的错误。首先,将整个堆栈空间初始化为一个已知值(除了0x00或0xff以外的其他值,因为这些值已经很常见了),然后运行系统一段时间以查看有多少堆栈空间未使用,这可能会有所帮助。


这些是非常好的选择。我会指出,您永远不要在嵌入式系统中使用malloc。
Kortuk

1
@Kortuk这取决于您对嵌入式的定义以及正在执行的任务
Toby Jaffey 2010年

1
@joby,是的,我知道。在重启次数为0且缺少linux之类的OS的系统中,Malloc可能非常糟糕。
Kortuk

没有动态内存分配,没有使用malloc和calloc的地方。我还检查了堆分配,并且已经将其设置为0,因此没有堆分配。当前分配的堆栈大小为254字节,中断堆栈的大小为128字节。
IntelliChick 2010年

5

您的代码是否使用浮点数学?您可能仅可以使用整数数学来重新实现算法,并消除使用C浮点库的开销。例如,在某些应用中,诸如正弦,对数,exp之类的函数可以用整数多项式近似代替。

您的代码是否将大型查询表用于任何算法,例如CRC计算?您可以尝试替换即时计算值的算法的其他版本,而不是使用查找表。需要注意的是,较小的算法很可能较慢,因此请确保您有足够的CPU周期。

您的代码中是否包含大量常量数据,例如字符串表,HTML页面或像素图形(图标)?如果足够大(例如10 kB),则可能有必要实施一个非常简单的压缩方案来缩小数据并在需要时即时对其进行解压缩。


有2个小的查找表,不幸的是,它们都不会有1万个。而且也没有使用浮点数学。。:(感谢您的建议,虽然他们是优秀的。
IntelliChick

2

您可以尝试重新安排很多代码,使样式更紧凑。这在很大程度上取决于代码在做什么。关键是要找到相似的事物,然后彼此重新实现。极端的情况是使用像Forth这样的高级语言,与C或汇编语言相比,使用这种语言可以更轻松地实现更高的代码密度。

这是M16C的Forth


2

设置编译器的优化级别。许多IDE都具有允许代码大小优化的设置,但会浪费编译时间(在某些情况下甚至可能浪费处理时间)。他们可以通过几次重新运行优化器,搜索不太常见的可优化模式以及其他一些可能不需要临时/调试编译的技巧来完成代码压缩。通常,默认情况下,编译器设置为中等优化级别。在设置中进行挖掘,您应该能够找到一些基于整数的优化比例。


1
目前已优化为最大尺寸。:) 还是)感谢你的建议。:)
IntelliChick 2010年

2

如果您已经在使用IAR之类的专业级编译器,那么我认为您将难以通过少量的低级代码调整来节省大量费用-您需要更多地着眼于删除功能或进行大型以更有效的方式重写零件。您需要成为一个比原始版本编写者都要聪明的编码器...至于RAM,您需要非常仔细地了解当前的使用方式,并查看是否有覆盖该RAM的范围。在不同的时间有不同的事情(工会很方便)。IAR在ARM / AVR中的默认堆大小和堆栈大小我已经过分夸大了,因此这些将是第一个要看的东西。


谢谢迈克。该代码已经在大多数地方使用了联合体,但是我将看看其他地方,这可能仍然会有所帮助。我还将查看所选的堆栈大小,看看是否可以对其进行优化。
IntelliChick 2010年

我怎么知道什么堆栈大小合适?
IntelliChick 2010年

2

其他需要检查的内容-某些体系结构上的某些编译器将常量复制到RAM-通常在访问闪存常量的速度较慢/困难时使用(例如AVR),例如IAR的AVR编译器需要_ _闪断限定符才能将常量复制到RAM)


谢谢迈克。是的,我已经检查过-对于M16C IAR C编译器,它称为“可写常量”选项。它将常量从ROM复制到RAM。我的项目未选中此选项。但是真正有效的检查!谢谢。
IntelliChick 2010年

1

如果您的处理器不支持参数/本地堆栈的硬件,但是编译器仍然尝试实现运行时参数堆栈,并且如果您的代码不需要重新输入,则可以保存代码通过静态分配自动变量来节省空间。在某些情况下,必须手动完成;在其他情况下,编译器指令可以做到这一点。有效的手动分配将需要在例程之间共享变量。必须仔细进行这种共享,以确保没有例程使用另一个例程认为在“范围内”的变量,但是在某些情况下,代码大小的好处可能很重要。

某些处理器具有调用约定,这些约定可能会使某些参数传递样式比其他一些更有效。例如,在PIC18控制器上,如果例程采用一个单字节参数,则可以将其传递到寄存器中;否则,可能会将其传递给寄存器。如果花费更多,则所有参数都必须在RAM中传递。如果例程将使用两个一个字节的参数,则最有效的做法是在全局变量中“传递”一个,然后将另一个作为参数传递。通过广泛使用的例程,可以节省更多的费用。如果通过global传递的参数是单个位标志,或者通常具有0或255的值(因为存在将0或255存储到RAM中的特殊指令),则它们可能特别重要。

在ARM上,将经常使用的全局变量放入结构中可以显着减小代码大小并提高性能。如果A,B,C,D和E是单独的全局变量,则使用所有变量的代码必须将每个变量的地址加载到寄存器中。如果没有足够的寄存器,则可能有必要多次重载这些地址。相反,如果它们是同一全局结构MyStuff的一部分,则使用MyStuff.A,MyStuff.B等的代码可以仅加载一次MyStuff的地址。大赢


1

1.如果您的代码依赖于许多结构,请确保结构成员从占用最多内存的结构成员排序到最少。

例如:“ uint32_t uint16_t uint8_t”而不是“ uint16_t uint8_t uint32_t”

这将确保最小的结构填充。

2.在适用的情况下将const用于变量。这样可以确保这些变量位于ROM中,而不会占用RAM


1

我已经成功使用了一些(也许很明显)的技巧来压缩一些客户的代码:

  1. 将标志压缩到位字段或位掩码中。这可能是有益的,因为通常布尔值存储为整数,从而浪费内存。这将节省RAM和ROM,通常不会由编译器完成。

  2. 在代码中寻找冗余,并使用循环或函数执行重复的语句。

  3. 我还if(x==enum_entry) <assignment>通过注意将枚举项用作数组索引,通过将常量中的许多语句替换为索引数组来节省了一些ROM。


0

如果可以,请使用内联函数或编译器宏,而不要使用小型函数。有传递参数的大小和速度开销,可以通过使函数内联来解决。


1
对于仅调用一次的函数,任何体面的编译器都应自动执行此操作。
mikeselectricstuff,2010年

5
我发现内联通常对于速度优化更有用,并且通常以增加大小为代价。
Craig McQueen 2010年

内联通常会增加代码的大小,除了像int get_a(struct x) {return x.a;}
Dmitry Grigoryev

0

将局部变量更改为与CPU寄存器相同的大小。

如果CPU是32位,即使最大值永远不会超过255,也请使用32位变量。我使用的是8位变量,编译器将添加代码以掩盖高24位。

我首先要看的是for循环变量。

for( i = 0; i < 100; i++ )

对于8位变量,这似乎是个好地方,但是32位变量可能会产生较少的代码。


这样可以节省代码,但会占用RAM。
mikeselectricstuff,2010年

仅当该函数调用位于调用跟踪的最长分支中时,它才会占用RAM。否则,它将重用某些其他功能已经需要的堆栈空间。
罗伯特2010年

2
如果它是局部变量,通常为true。如果RAM不足,则全局变量(尤其是数组)的大小是开始寻求节省的好地方。
mikeselectricstuff,2010年

1
有趣的是,另一种可能性是用符号替换无符号变量。如果编译器将无符号short优化为32位寄存器,则它必须添加代码以确保其值从65535换为零。但是,如果编译器优化了寄存器的有符号短路,则不需要此类代码。因为不能保证将短路增加到32767以上会发生什么,所以不需要编译器发出代码来处理。由于这个原因,在至少两个我研究过的ARM编译器上,带符号的短代码可以小于无符号的短代码。
supercat
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.