【问题标题】:How to optimize or reduce RAM size in embedded system software?如何优化或减少嵌入式系统软件中的 RAM 大小?
【发布时间】:2011-08-06 19:50:23
【问题描述】:

我正在从事汽车领域的嵌入式软件项目。在我的一个项目中,应用软件消耗了将近 99% 的 RAM 内存。实际可用的 RAM 大小为 12KB。我们使用 TMS470R1B1 Titan F05 微控制器。我做了一些优化,比如在软件中查找未使用的消息并删除它们,但仍然不值得减少 RAM。您能否建议一些通过一些软件优化来减少 RAM 的好方法?

【问题讨论】:

  • 你应该先解雇喜欢说“优化是万恶之源”的人。
  • 您应该描述您当前的结构。您是否使用动态分配,是否有 RTOS,您的地图文件中是否有大消费者?
  • 汽车行业不允许动态内存分配,因为 MISRA-C 禁止这样做。

标签: embedded


【解决方案1】:

与速度优化不同,RAM 优化可能需要“这里一点点,那里一点点”贯穿整个代码。另一方面,结果可能是一些“容易实现的成果”。

数组和查找表

数组和查找表可能是很好的“容易实现的目标”。如果您可以从链接器获取内存映射,请检查 RAM 中的大型项目。

检查未正确使用 const 声明的查找表,这会将它们放入 RAM 而不是 ROM。尤其要注意指针的查找表,它需要在* 的正确一侧使用const,或者可能需要两个 const 声明。例如:

const my_struct_t * param_lookup[] = {...};  // Table is in RAM!
my_struct_t * const param_lookup[] = {...};  // In ROM
const char * const strings[] = {...};    // Two const may be needed; also in ROM

堆栈和堆

也许您的链接器配置为堆和堆栈保留了大量 RAM,比您的应用程序所需的要大。

如果你不使用堆,你可以消除它。

如果您测量您的堆栈使用情况并且它在分配范围内,您可以减少分配。对于 ARM 处理器,可以有多个堆栈,用于几种操作模式,您可能会发现分配给异常或中断操作模式的堆栈比需要的大。

其他

如果您已经检查了容易节省的费用,但仍然需要更多,您可能需要检查您的代码并“这里一点,那里一点”进行节省。您可以检查以下内容:

全局变量与局部变量

检查static 或全局变量的不必要使用,其中可以使用局部变量(在堆栈上)来代替。我见过在函数中需要一个小的临时数组的代码,它被声明为static,显然是因为“它会占用太多的堆栈空间”。如果这种情况在代码中发生的次数足够多,它实际上会节省总体内存使用量,以使这些变量再次成为本地变量。它可能需要增加堆栈大小,但会在减少的全局/静态变量上节省更多内存。 (作为附带的好处,这些函数更有可能是可重入的、线程安全的。)

较小的变量

可以更小的变量,例如int16_t (short) 或 int8_t (char) 而不是 int32_t (int)。

枚举变量大小

enum 变量大小可能比需要的大。我不记得 ARM 编译器通常做什么,但我过去使用的一些编译器默认将 enum 变量设为 2 个字节,尽管 enum 定义实际上只需要 1 个字节来存储其范围。检查编译器设置。

算法实现

重做你的算法。一些算法具有一系列可能的实现,具有速度/内存权衡。例如。 AES 加密可以使用即时密钥计算,这意味着您不必将整个扩展密钥保存在内存中。这样可以节省内存,但速度较慢。

【讨论】:

    【解决方案2】:

    删除未使用的字符串文字不会对 RAM 的使用产生任何影响,因为它们不是存储在 RAM 中而是存储在 ROM 中。代码也是如此。

    您需要做的是减少实际变量,并可能减少堆栈/堆栈的大小。我会寻找可以调整大小和未使用变量的数组。另外,最好避免动态分配,因为memory fragmentation的危险。

    除此之外,您还需要确保将查找表等常量数据存储在 ROM 中。这通常可以使用const 关键字来实现。

    【讨论】:

    • 在微控制器中处理 RAM 优化可能会很痛苦......除此之外,O.P 还应该检查内存泄漏,确保正确释放 malloc 的内存。只要不再需要,就丢弃任何一次性物品。
    • Paulo,这不是运行时的考虑,此时堆 malloc()s 会开始失败吗?我认为 OP 是在讨论编译时报告的内存使用情况,在这种情况下,内存泄漏、malloc 和处置毫无意义。
    【解决方案3】:

    确保链接器生成一个 MAP 文件 - 它会显示 RAM 的使用位置。有时您可以找到诸如保存在 RAM 中的字符串文字/常量之类的东西。有时您会发现其他人放在那里的未使用的数组/变量。

    如果您有链接器映射文件,那么首先攻击使用最多 RAM 的模块也很容易。

    【讨论】:

      【解决方案4】:

      以下是我在 Cell 上使用的技巧:

      • 从显而易见的开始:尽可能将 32 位字压缩到 16 秒,重新排列结构以消除填充,减少任何数组中的松弛。如果您有任何超过八个结构的数组,则值得使用位域将它们压缩得更紧密。
      • 取消动态内存分配并使用静态池。恒定的内存占用更容易优化,并且您可以确保没有泄漏。
      • 严格限定本地分配,以使它们在堆栈上停留的时间不会超过它们必须的时间。一些编译器非常不善于识别您何时使用完一个变量,并将其留在堆栈中,直到函数返回。这对于外部函数中的大型对象可能会很糟糕,因为外部函数调用到树的更深处时,它们会占用他们不需要的持久内存。
      • alloca() 在函数返回之前不会清理,因此浪费堆栈的时间可能比您预期的要长。
      • 在编译器中启用函数体和常量合并,这样如果它看到八个具有相同值的不同常量,它只会将一个放在文本段中,并使用链接器为它们设置别名。
      • 针对大小优化可执行代码。如果你有一个硬性的实时截止日期,你确切地知道你的代码需要运行多快,所以如果你有任何备用性能,你可以在速度/大小上进行权衡,直到你达到那个点。滚动循环、将通用代码拉入函数等。在某些情况下,如果 prolog/epilog 开销大于函数体,您实际上可以通过内联某些函数获得空间改进

      我猜最后一个仅与将代码存储在 RAM 中的架构相关。

      【讨论】:

        【解决方案5】:

        w.r.t 函数,以下是优化 RAM 的句柄

        1. 确保深入分析传递给函数的参数数量。在根据 AAPCS(ARM arch 过程调用标准)的 ARM 架构上,最多可以使用寄存器传递 4 个参数,其余参数将被压入堆栈。

        2. 还要考虑使用全局而不是将数据传递给使用相同参数最常调用的函数的情况。

        3. 函数调用越深,堆栈的使用越重。使用任何静态分析工具,了解最差的演员函数调用路径并寻找减少它的场所。当函数 A 调用函数 B 时,B 调用函数 C,C 又调用 D,D 又调用 E 并更深入。在这种情况下,寄存器不能在所有级别传递参数,因此显然会使用堆栈。

        4. 在适用的情况下尝试将两个参数合二为一的场所。请记住,ARM 中的所有寄存器都是 32 位的,因此还可以进行进一步优化。 void abc(bool a, bool b, uint16_t c, uint32_t d, uint8_t e)// 使用寄存器和堆栈 void abc(uint8 ab, uint16_t c, uint32_t d, uint8_t e)//前2个参数可以合并。所以总共可以使用寄存器传递4个参数

        5. 重新审视嵌套中断向量。在任何架构中,我们都会使用暂存寄存器和保留寄存器。保留寄存器是在服务中断之前需要保存的东西。在嵌套中断的情况下,它将需要巨大的堆栈空间来备份保留的寄存器进出堆栈。

        6. 如果将结构等类型的对象按值传递给函数,那么它会推送大量数据(取决于结构大小),这很容易占用堆栈空间。这可以更改为通过引用传递。

        问候

        巴拉尼库马尔文卡特桑

        【讨论】:

          【解决方案6】:

          添加到以前的答案。

          如果您从 RAM 运行程序以获得更快的执行速度,您可以创建一个用户定义的部分,其中包含您确定在系统启动后它不会多次运行的所有初始化例程。执行完所有初始化函数后,您可以重新将该区域用作堆。

          这可以应用于在程序的某个阶段之后被确定为无用的数据部分。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-11-20
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-01-08
            相关资源
            最近更新 更多