我一直在为我们的特定产品开发功能。有人要求将相同功能移植到另一产品。该产品基于M16C微控制器,该微控制器传统上具有64K闪存和2k RAM。
它是一种成熟的产品,因此,只剩下132字节的闪存和2字节的RAM。
要移植请求的功能(功能本身已经过优化),我需要1400字节的Flash和〜200字节的RAM。
有没有人对如何通过代码压缩检索这些字节有任何建议?当我试图压缩已经存在的工作代码时,我需要寻找什么特定的东西?
任何想法将不胜感激。
谢谢。
我一直在为我们的特定产品开发功能。有人要求将相同功能移植到另一产品。该产品基于M16C微控制器,该微控制器传统上具有64K闪存和2k RAM。
它是一种成熟的产品,因此,只剩下132字节的闪存和2字节的RAM。
要移植请求的功能(功能本身已经过优化),我需要1400字节的Flash和〜200字节的RAM。
有没有人对如何通过代码压缩检索这些字节有任何建议?当我试图压缩已经存在的工作代码时,我需要寻找什么特定的东西?
任何想法将不胜感激。
谢谢。
Answers:
您有两种选择:首先是查找冗余代码,然后将其移至单个调用中以消除重复。第二个是删除功能。
仔细查看.map文件,看看是否有一些可以删除或重写的函数。还要确保确实需要使用正在使用的库调用。
诸如除法和乘法之类的某些事情可以带来很多代码,但是使用移位和更好地使用常量可以使代码更小。还可以看一下字符串常量和printf
s之类的东西。例如,每个printf
都会占用您的rom,但您可能可以拥有几个共享格式的字符串,而不必一次又一次地重复该字符串常量。
对于内存,请查看是否可以摆脱全局变量,而在函数中使用自动变量。还要避免在主函数中使用尽可能多的变量,因为它们就像全局变量一样会消耗内存。
始终值得一看清单文件(汇编程序)输出,以查找您的特定编译器特别擅长的事情。
例如,您可能会发现局部变量非常昂贵,并且如果应用程序足够简单就值得承担风险,那么将一些循环计数器移入静态变量可能会节省大量代码。
或数组索引可能非常昂贵,但指针操作便宜得多。或相反亦然。
但是看汇编语言是第一步。
例如,-Os
GCC中的编译器优化可在速度和代码大小之间实现最佳平衡。避免使用-O3
,因为它可能会增加代码大小。
对于RAM,请检查所有变量的范围-是否在可以使用char的位置使用int?缓冲区是否大于所需的大小?
代码压缩非常依赖于应用程序和编码样式。您剩下的金额表明,也许代码已经经过了一些压缩,但这意味着几乎没有剩余了。
还仔细看一下整体功能-是否有未真正使用且可以抛弃的东西?
如果这是一个旧项目,但是编译器是从那时开始开发的,则可能是较新的编译器可能会生成较小的代码
始终值得检查编译器手册中提供的选项以优化空间。
对于gcc -ffunction-sections
并-fdata-sections
带有--gc-sections
链接器标志非常适合剥离死代码。
以下是一些其他出色的技巧(针对AVR)
您可以检查分配的堆栈空间和堆空间量。如果其中一个或两个都被过度分配,则可能能够获得大量的RAM。
我的猜测是对一个项目适合进入的RAM 2K下手没有动态内存分配(使用的malloc
,calloc
等等)。如果是这种情况,则可以假定原始作者为堆分配了一些RAM,从而完全摆脱了堆。
您必须非常小心地减小堆栈大小,因为这会导致很难发现的错误。首先,将整个堆栈空间初始化为一个已知值(除了0x00或0xff以外的其他值,因为这些值已经很常见了),然后运行系统一段时间以查看有多少堆栈空间未使用,这可能会有所帮助。
您的代码是否使用浮点数学?您可能仅可以使用整数数学来重新实现算法,并消除使用C浮点库的开销。例如,在某些应用中,诸如正弦,对数,exp之类的函数可以用整数多项式近似代替。
您的代码是否将大型查询表用于任何算法,例如CRC计算?您可以尝试替换即时计算值的算法的其他版本,而不是使用查找表。需要注意的是,较小的算法很可能较慢,因此请确保您有足够的CPU周期。
您的代码中是否包含大量常量数据,例如字符串表,HTML页面或像素图形(图标)?如果足够大(例如10 kB),则可能有必要实施一个非常简单的压缩方案来缩小数据并在需要时即时对其进行解压缩。
您可以尝试重新安排很多代码,使样式更紧凑。这在很大程度上取决于代码在做什么。关键是要找到相似的事物,然后彼此重新实现。极端的情况是使用像Forth这样的高级语言,与C或汇编语言相比,使用这种语言可以更轻松地实现更高的代码密度。
这是M16C的Forth。
设置编译器的优化级别。许多IDE都具有允许代码大小优化的设置,但会浪费编译时间(在某些情况下甚至可能浪费处理时间)。他们可以通过几次重新运行优化器,搜索不太常见的可优化模式以及其他一些可能不需要临时/调试编译的技巧来完成代码压缩。通常,默认情况下,编译器设置为中等优化级别。在设置中进行挖掘,您应该能够找到一些基于整数的优化比例。
如果您已经在使用IAR之类的专业级编译器,那么我认为您将难以通过少量的低级代码调整来节省大量费用-您需要更多地着眼于删除功能或进行大型以更有效的方式重写零件。您需要成为一个比原始版本编写者都要聪明的编码器...至于RAM,您需要非常仔细地了解当前的使用方式,并查看是否有覆盖该RAM的范围。在不同的时间有不同的事情(工会很方便)。IAR在ARM / AVR中的默认堆大小和堆栈大小我已经过分夸大了,因此这些将是第一个要看的东西。
其他需要检查的内容-某些体系结构上的某些编译器将常量复制到RAM-通常在访问闪存常量的速度较慢/困难时使用(例如AVR),例如IAR的AVR编译器需要_ _闪断限定符才能不将常量复制到RAM)
如果您的处理器不支持参数/本地堆栈的硬件,但是编译器仍然尝试实现运行时参数堆栈,并且如果您的代码不需要重新输入,则可以保存代码通过静态分配自动变量来节省空间。在某些情况下,必须手动完成;在其他情况下,编译器指令可以做到这一点。有效的手动分配将需要在例程之间共享变量。必须仔细进行这种共享,以确保没有例程使用另一个例程认为在“范围内”的变量,但是在某些情况下,代码大小的好处可能很重要。
某些处理器具有调用约定,这些约定可能会使某些参数传递样式比其他一些更有效。例如,在PIC18控制器上,如果例程采用一个单字节参数,则可以将其传递到寄存器中;否则,可能会将其传递给寄存器。如果花费更多,则所有参数都必须在RAM中传递。如果例程将使用两个一个字节的参数,则最有效的做法是在全局变量中“传递”一个,然后将另一个作为参数传递。通过广泛使用的例程,可以节省更多的费用。如果通过global传递的参数是单个位标志,或者通常具有0或255的值(因为存在将0或255存储到RAM中的特殊指令),则它们可能特别重要。
在ARM上,将经常使用的全局变量放入结构中可以显着减小代码大小并提高性能。如果A,B,C,D和E是单独的全局变量,则使用所有变量的代码必须将每个变量的地址加载到寄存器中。如果没有足够的寄存器,则可能有必要多次重载这些地址。相反,如果它们是同一全局结构MyStuff的一部分,则使用MyStuff.A,MyStuff.B等的代码可以仅加载一次MyStuff的地址。大赢
如果可以,请使用内联函数或编译器宏,而不要使用小型函数。有传递参数的大小和速度开销,可以通过使函数内联来解决。
int get_a(struct x) {return x.a;}
将局部变量更改为与CPU寄存器相同的大小。
如果CPU是32位,即使最大值永远不会超过255,也请使用32位变量。我使用的是8位变量,编译器将添加代码以掩盖高24位。
我首先要看的是for循环变量。
for( i = 0; i < 100; i++ )
对于8位变量,这似乎是个好地方,但是32位变量可能会产生较少的代码。