“Memory Efficient C programming”的最佳实践是什么? 主要用于嵌入式/移动设备应该是低内存消耗的准则?
我猜应该有单独的指导方针a)代码存储器b)数据存储器
答案 0 :(得分:27)
在C中,在更简单的层面上,请考虑以下内容;
答案 1 :(得分:20)
我发现一些建议在使用嵌入式系统时很有用:
确保使用const
实际声明任何查找表或其他常量数据。如果使用const
,那么数据可以存储在只读(例如闪存或EEPROM)存储器中,否则数据必须在启动时复制到RAM,这会占用闪存和RAM空间。设置链接器选项以便生成映射文件,并研究此文件以准确查看内存映射中数据的分配位置。
确保您使用的是所有可用的内存区域。例如,微控制器通常具有可以使用的板载存储器(也可以比外部RAM更快地访问)。您应该能够使用编译器和链接器选项设置来控制分配代码和数据的内存区域。
要减少代码大小,请检查编译器的优化设置。大多数编译器都有开关来优化速度或代码大小。值得尝试使用这些选项来查看编译代码的大小是否可以减少。显然,尽可能消除重复的代码。
检查系统需要多少堆栈内存并相应地调整链接器内存分配(请参阅this question的答案)。要减少堆栈使用量,请避免在堆栈上放置大型数据结构(无论“大”值与您有什么关系)。
答案 2 :(得分:16)
确保尽可能使用定点/整数数学。当简单的缩放整数数学就足够时,许多开发人员使用浮点数学(以及缓慢的性能和大型库和内存使用)。
答案 3 :(得分:9)
所有好建议。以下是我发现有用的一些设计方法。
为专用字节码指令集编写解释器,并在该指令集中尽可能多地写入程序。如果某些操作需要高性能,请将它们设置为本机代码并从解释器中调用它们。
如果部分输入数据很少发生变化,您可以使用外部代码生成器创建临时程序。这将比一般程序更小,运行速度更快,无需为很少变化的输入分配存储空间。
如果能够存储绝对最小的数据结构,愿意浪费大量的周期。通常你会发现表现很差。
答案 4 :(得分:8)
您很可能需要仔细选择算法。目标是具有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)。 / p>
答案 5 :(得分:8)
使用您自己的内存分配器(或小心使用系统的分配器)避免内存碎片。
一种方法是使用'slab allocator'(例如参见此article)和多个不同大小的对象的内存池。
答案 6 :(得分:7)
预先分配所有内存(即除了启动初始化之外没有malloc调用)肯定有助于确定性内存使用。否则,不同的架构提供了帮助的技术。例如,某些ARM处理器提供备用指令集(Thumb),通过使用16位指令而不是正常的32位,几乎将代码大小减半。当然,这样做会牺牲速度......
答案 7 :(得分:6)
可以在字节到达时对流执行某些解析操作,而不是复制到缓冲区和解析。
这方面的一些例子:
答案 8 :(得分:5)
1)在开始项目之前,建立一种测量您正在使用的内存量的方法,最好是基于每个组件。这样,每次进行更改时,都可以看到它对内存使用的影响。您无法优化无法衡量的内容。
2)如果项目已经成熟并且达到内存限制(或移植到内存较少的设备),请找出你正在使用内存的内容。
我的经验是,在修复过大的应用程序时,几乎所有重要的优化都来自于少量的更改:减少缓存大小,去除一些纹理(当然这是一个需要利益相关方协议的功能变更,即会议,因此可能在您的时间方面效率不高),重新采样音频,减少自定义分配堆的前期大小,找到释放仅暂时使用的资源的方法,并在需要时重新加载它们。偶尔你会发现一些64字节的结构,可以减少到16,或者其他什么,但这很少是最低挂的结果。如果您知道应用程序中最大的列表和数组是什么,那么您首先要知道要查看哪些结构。
哦是的:找到并修复内存泄漏。你可以在不牺牲性能的情况下重获任何记忆,这是一个很好的开始。
我过去花了很多时间来担心代码大小。主要考虑因素(除了:确保在构建时测量它以便您可以看到它发生变化),是:
1)找出引用的代码,以及引用的代码。如果您发现整个XML库被链接到您的应用程序只是为了解析一个双元素的配置文件,请考虑更改配置文件格式和/或编写您自己的简单解析器。如果可以的话,使用源或二进制分析来绘制一个大的依赖图,并查找只有少量用户的大型组件:可能只需要很少的代码重写即可将这些组件剪掉。准备好扮演外交官:如果你的应用程序中有两个不同的组件使用XML,并且你想要削减它,那么就是两个人你必须说服手工滚动的好处,这些东西目前是一个值得信赖的,现成的库
2)使用编译器选项进行混乱。请参阅特定于平台的文档。例如,您可能希望减少由于内联而导致的默认可接受代码大小增加,并且至少您可以告诉编译器只应用通常不会增加代码大小的优化。
3)尽可能利用目标平台上已有的库,即使这意味着编写适配器层。在上面的XML示例中,您可能会发现在目标平台上始终存在内存中的XML库,因为操作系统会使用它,在这种情况下会动态链接到它。
4)正如其他人提到的,拇指模式可以帮助ARM。如果您只将它用于非性能关键的代码,并将关键例程保留在ARM中,那么您将不会注意到差异。
最后,如果您对设备有足够的控制权,可能会有巧妙的技巧。 UI只允许一次运行一个应用程序?卸载您的应用不需要的所有驱动程序和服务。屏幕是双缓冲的,但您的应用程序是否与刷新周期同步?您可以回收整个屏幕缓冲区。
答案 9 :(得分:3)
答案 10 :(得分:2)
减少字符串常量并消除尽可能多的字符串常量以减少代码空间
仔细考虑算法与需要的查找表的权衡
了解如何分配不同类型的变量。
答案 11 :(得分:1)
我有关于此主题的嵌入式系统会议的演示。它是从2001年开始的,但它仍然非常适用。请参阅paper。
此外,如果你可以选择目标设备的架构,可以使用带有Thumb V2的现代ARM,带有VLE的PowerPC,带有MIPS16的MIPS,或者选择像Infineon TriCore或SH这样的已知紧凑型目标。家庭是一个非常好的选择。更不用说NEC V850E系列非常紧凑。或者转移到具有出色代码紧凑性的AVR(但是是8位机器)。除固定长度的32位RISC之外的任何东西都是不错的选择!
答案 12 :(得分:1)
除了其他人给出的建议外,请记住在函数中声明的局部变量通常会在堆栈中分配。
如果堆栈内存有限,或者您希望减小堆栈大小以便为更多堆或全局RAM腾出空间,请考虑以下事项:
将大型局部变量转换为全局变量(减少使用的堆栈量,但会增加使用的全局RAM量)。变量可以声明:
reentrant
环境,则必须警惕preemptive
代码的问题。许多嵌入式系统没有堆栈监视器诊断功能,以确保捕获stack overflow,因此需要进行一些分析。
PS:适当使用 Stack Overflow 的奖金?
答案 13 :(得分:1)
另一个有用的技巧是使用#define 代替其值不会改变的变量。例如:如果某些东西被定义为 const,用 #define 替换它,因为它们在预处理器阶段被解码并且不占用空间。定义的任何其他数据类型都需要内存空间。这将有助于减少内存使用。这仅适用于不关心变量类型的情况,因为 #define 不会处理它。
答案 14 :(得分:0)
在应用程序中有用的一个技巧是创建一个下雨天的内存资金。在启动时分配单个块足够大,足以完成清理任务。如果malloc / new失败,请释放雨天基金并发布消息,让用户知道资源紧张,他们应该很快保存。这是1990年左右许多Mac应用程序中使用的一种技术。
答案 15 :(得分:0)
限制内存需求的一个好方法是尽可能地依赖libc或其他可以动态链接的标准库。您必须在项目中包含的每个额外的DLL或共享对象都是您可以避免刻录的重要内存。
另外,在适用的情况下,使用联合和位字段只加载程序在内存中处理的数据部分,并确保使用-Os进行编译(使用gcc;或者编译器的等效编译器) )切换到优化程序大小。