什么是"内存高效C编程"的最佳实践.主要用于嵌入式/移动设备应该是低内存消耗的准则?
我猜应该有单独的指导方针a)代码存储器b)数据存储器
在C中,在更简单的层面上,考虑以下因素;
使用#pragma pack(1)对结构进行字节对齐
使用结构可以包含不同类型数据的联合
使用位字段而不是整数来存储标志和小整数
避免使用固定长度的字符数组来存储字符串,实现字符串池和使用指针.
存储对枚举字符串列表的引用(例如字体名称)时,将索引存储到列表中而不是字符串中
使用动态内存分配时,请提前计算所需的元素数以避免重新分配.
我发现在使用嵌入式系统时有一些建议是有用的:
确保使用实际声明任何查找表或其他常量数据const
.如果const
使用,那么数据可以存储在只读(例如,闪存或EEPROM)存储器中,否则数据必须在启动时复制到RAM,这会占用闪存和RAM空间.设置链接器选项以便生成映射文件,并研究此文件以准确查看在内存映射中分配数据的位置.
确保您使用的是所有可用的内存区域.例如,微控制器通常具有可以使用的板载存储器(也可以比外部RAM更快地访问).您应该能够使用编译器和链接器选项设置来控制分配代码和数据的内存区域.
要减少代码大小,请检查编译器的优化设置.大多数编译器都有开关来优化速度或代码大小.值得尝试使用这些选项来查看编译代码的大小是否可以减少.显然,尽可能消除重复的代码.
检查系统需要多少堆栈内存并相应地调整链接器内存分配(请参阅此问题的答案).要减少堆栈使用量,请避免在堆栈上放置大型数据结构(因为"大"的任何值与您相关).
确保尽可能使用定点/整数数学.当简单的缩放整数数学就足够时,许多开发人员使用浮点数学(以及缓慢的性能和大型库和内存使用).
所有好建议.以下是我发现有用的一些设计方法.
字节编码
为专用字节码指令集编写解释器,并在该指令集中尽可能多地写入程序.如果某些操作需要高性能,请将它们设置为本机代码并从解释器中调用它们.
代码生成
如果输入数据的一部分很少发生变化,您可以使用外部代码生成器创建临时程序.这将比一般程序更小,运行速度更快,无需为很少变化的输入分配存储空间.
是一个数据仇恨者
如果能够存储绝对最小的数据结构,愿意浪费大量的周期.通常你会发现性能很差.
您很可能需要仔细选择算法.针对具有O(1)或O(log n)内存使用(即低)的算法.例如,连续可调整大小的数组(例如std::vector
)在大多数情况下比链接列表需要更少的内存.
有时,使用查找表可能对代码大小和速度更有利.如果在LUT中只需要64个条目,那么sin/cos/tan(使用对称性!)的16*4字节与较大的sin/cos/tan函数相比较.
压缩有时会有所帮助.像RLE这样的简单算法在按顺序读取时很容易压缩/解压缩.
如果您正在处理图形或音频,请考虑不同的格式.调色板或bitpacked*图形可能是质量的良好折衷,并且可以在许多图像之间共享调色板,从而进一步减小数据大小.音频可以从16位减少到8位甚至4位,立体声可以转换为单声道.采样率可以从44.1KHz降低到22kHz或11kHz.这些音频转换大大减少了它们的数据大小(而且,遗憾的是,质量)并且是微不足道的(除了重新采样,但这就是音频软件用于=]).
*我想你可以把它压缩.图形的位分组通常是指减少每个通道的位数,因此每个像素可以容纳两个字节(例如RGB565或ARGB155)或一个(ARGB232或RGB332)原始的三个或四个(分别为RGB888或ARGB8888).
使用自己的内存分配器(或仔细使用系统的分配器)避免内存碎片.
一种方法是使用'slab allocator'(例如参见本文)和多个不同大小的对象的内存池.
预先分配所有内存(即除启动初始化之外没有malloc调用)肯定有助于确定性内存使用.否则,不同的架构提供了帮助的技术.例如,某些ARM处理器提供备用指令集(Thumb),通过使用16位指令而不是正常的32位,几乎将代码大小减半.当然,这样做会牺牲速度......
当字节到达时,可以对流执行一些解析操作,而不是复制到缓冲区和解析.
一些例子:
使用状态机解析NMEA流,仅将所需字段收集到更高效的结构中.
使用SAX而不是DOM解析XML.
1)在开始项目之前,请构建一种可以衡量正在使用多少内存的方式,最好以每个组件为基础。这样,每次进行更改时,您都可以看到其对内存使用的影响。您无法优化无法衡量的内容。
2)如果项目已经成熟并且达到内存限制,(或已移植到内存较少的设备上),请找出您正在使用的内存。
我的经验是,修复超大应用程序时,几乎所有的重大优化都来自少量更改:减小缓存大小,去除一些纹理(当然,这是一项功能更改,需要利益相关者达成一致,即开会,因此在时间上效率不高),重新采样音频,减少自定义分配的堆的前期大小,找到释放仅临时使用的资源并在需要时重新加载的方法。有时,您会发现一些结构,该结构为64个字节,可以减少为16个字节或其他内容,但这很少是挂得最低的成果。但是,如果您知道应用程序中最大的列表和数组是什么,那么您就会知道首先要查看的结构。
哦,是的:查找并修复内存泄漏。您可以在不牺牲性能的情况下恢复的任何内存都是一个不错的开始。
过去,我花了很多时间来担心代码的大小。主要考虑因素(除了:确保在构建时对其进行度量,以便可以看到它的变化)是:
1)找出引用了什么代码,引用了什么。如果您发现整个XML库仅链接到您的应用程序以解析两个元素的配置文件,请考虑更改配置文件格式和/或编写自己的琐碎解析器。如果可以,请使用源分析或二进制分析来绘制一个大的依赖关系图,并只用少量的用户来查找大型组件:可能只需要少量的代码重写就可以将它们剪裁掉。准备扮演外交官的角色:如果您的应用程序中两个不同的组件都使用XML,并且您想削减它,那么这两个人就必须让您相信手动滚动当前已受信任的现成库的好处。
2)混乱的编译器选项。请查阅您特定于平台的文档。例如,由于内联,您可能希望减少默认的可接受代码大小增加,并且至少在GCC上,您可以告诉编译器仅应用通常不会增加代码大小的优化。
3)尽可能利用目标平台上已经存在的库,即使这意味着编写适配器层。在上面的XML示例中,您可能会始终在目标平台上发现内存中始终存在一个XML库,因为OS会使用它,在这种情况下会动态链接到它。
4)正如其他人所述,拇指模式可以在ARM上提供帮助。如果仅将其用于对性能要求不高的代码,而将关键例程保留在ARM中,则您不会注意到其中的区别。
最后,如果您对设备有足够的控制权,则可以玩一些巧妙的技巧。用户界面一次只允许一个应用程序运行?卸载您的应用不需要的所有驱动程序和服务。屏幕是双缓冲的,但是您的应用已同步到刷新周期了吗?您也许可以收回整个屏幕缓冲区。