我使用了太多的RAM。如何测量?


19

我想知道我在项目中使用了多少RAM,据我所知,没有办法真正解决这个问题(除了自己进行计算之外)。我进入了一个相当大的项目的阶段,在此我已经确定我的RAM用完了。

我已经确定了这一点,因为我可以添加一个节,然后在代码中的其他地方毫无理由地打破所有地狱。如果我有#ifndef其他问题,它将再次起作用。新代码在程序上没有任何问题。

我怀疑有一段时间我将要用完可用的RAM。我不认为我使用了太多堆栈(尽管有可能),确定我实际使用多少RAM的最佳方法是什么?

经历并尝试解决它,当我遇到枚举和结构时会遇到问题;它们需要多少内存?

第一次编辑:另外,自开始以来,我已经对草图进行了很多编辑,这些并不是我最初得到的实际结果,但是它们是我现在所得到的。

  text     data     bss     dec     hex filename
 17554      844     449   18847    499f HA15_20140317w.cpp.elf
 16316      694     409   17419    440b HA15_20140317w.cpp.elf
 17346      790     426   18562    4882 HA15_20140317w.cpp.elf

经过大量编辑后,第一行(带有文本17554)不起作用,第二行(带有文本16316)正在正常工作。

编辑:第三行可以正常工作,进行串行读取,执行新功能等。实际上,我删除了一些全局变量和重复的代码。我之所以这样说是因为(怀疑)这与每个sae的代码无关,而必须与RAM的使用有关。这使我回到了最初的问题,“如何最好地衡量它”,我仍在检查一些答案,谢谢。

我实际上如何解释以上信息?

到目前为止,我的理解是:

`TEXT` is program instruction memory
`DATA` is variables (unitialised?) in program memory
`BSS`  is variables occupying RAM

由于BSS大大少于1024字节,为什么第二个可以工作,而第一个却不能呢?如果DATA+BSS两者都超过1024。

重新编辑:我编辑了问题以包含代码,但是现在我删除了它,因为它确实与问题无关(可能是不良的编码习惯,变量声明等)。如果您确实想查看代码,则可以通过回顾这些代码来查看代码。我想回到前面的问题,该问题更多地基于:如何衡量RAM使用率。


我想我会添加,在过去的几周中添加了新的代码段,然后对其进行了优化,直到它起作用为止,但是现在我只添加了一个半字节的var变量,我就完成了... :(
Madivad

String在程序中使用类型吗?众所周知,这会执行频繁的动态内存分配和释放,这可能会使堆碎片化到您可能没有剩余内存的地步。
jfpoilpret 2014年

@jfpoilpret String由于开销,我远离s。我很高兴有机会为字符数组,这么说,我几乎总是定义一个固定大小的(目前我所有的字符数组,我有一个字节数组,因为我改变内容长度不同的重新编译,是不是纯粹的。
Madivad 2014年

将您的代码发布到此处(如果太大,则粘贴到pastebin上)可能会发现您遇到的内存问题。
jfpoilpret 2014年

@jfpoilpret我无法真正发布代码,它很大而且很very肿,散布在16个文件中。我允许这个项目发展到超出要求的程度(这是几个项目合并在一起)。我现在开始进行分解,我相信这将有助于解决问题。尽管其中有些部分我需要人们关注(或指导我),但稍后再发布。
Madivad 2014年

Answers:


15

您可以使用AVRGCC提供的功能:监视堆栈使用情况

该功能旨在检查堆栈使用情况,但报告的内容是从未使用过的实际RAM(在执行过程中)。它通过用已知值(0xC5)“绘制”(填充)RAM,然后检查RAM区域以计数多少个字节仍具有相同的初始值来做到这一点。
该报告将显示未使用的RAM(最小可用RAM),因此您可以计算已使用的最大RAM(总RAM-报告的RAM)。

有两个功能:

  • StackPaint在初始化期间自动执行,并用0xC5值“绘制” RAM(可根据需要更改)。

  • 可以随时调用StackCount来计数未使用的RAM。

这是用法示例。并没有做太多,但是旨在说明如何使用这些功能。

// -----------------------------------------------------------------------------
extern uint8_t _end;
extern uint8_t __stack;

void StackPaint(void) __attribute__ ((naked)) __attribute__ ((section (".init1")));

void StackPaint(void)
{
#if 0
    uint8_t *p = &_end;

    while(p <= &__stack)
    {
        *p = 0xc5;
        p++;
    }
#else
    __asm volatile ("    ldi r30,lo8(_end)\n"
                    "    ldi r31,hi8(_end)\n"
                    "    ldi r24,lo8(0xc5)\n" /* STACK_CANARY = 0xc5 */
                    "    ldi r25,hi8(__stack)\n"
                    "    rjmp .cmp\n"
                    ".loop:\n"
                    "    st Z+,r24\n"
                    ".cmp:\n"
                    "    cpi r30,lo8(__stack)\n"
                    "    cpc r31,r25\n"
                    "    brlo .loop\n"
                    "    breq .loop"::);
#endif
} 


uint16_t StackCount(void)
{
    const uint8_t *p = &_end;
    uint16_t       c = 0;

    while(*p == 0xc5 && p <= &__stack)
    {
        p++;
        c++;
    }

    return c;
} 

// -----------------------------------------------------------------------------

void setup() {

Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:
Serial.println(StackCount(), DEC);  // calls StackCount() to report the unused RAM
delay(1000);
}

一段有趣的代码,谢谢。我确实使用过它,并且它暗示有600多个字节可用,但是当我将其埋入更深的子容器中时,它确实会减少,但不会消失。所以也许我的问题在其他地方。
Madivad 2014年

@Madivad请注意,这600多个字节表示直到调用StackCount为止的最小可用RAM量。调用的深度并没有多大区别,如果在调用StackCount之前执行了大多数代码和嵌套调用,则结果将是正确的。因此,例如,您可以让板子工作一会儿(只要能获得足够的代码覆盖率,或者最好是直到出现您描述的不良行为为止),然后按一个按钮以获取报告的RAM。如果足够,那不是问题的原因。
alexan_e 2014年

1
感谢@alexan_e,我在显示屏上创建了一个现在报告该区域的区域,因此在接下来的几天里,我会感兴趣地观看此数字,尤其是当它失败时!再次感谢
Madivad 2014年

@Madivad请注意,如果代码中使用了malloc()
alexan_e 2014年

感谢,我知道,已经提到过。据我所知,我没有使用它(我知道可能有一个库正在使用它,我还没有完全检查过)。
2014年

10

您在运行时使用内存可能遇到的主要问题是:

  • 堆中没有可用内存用于动态分配(mallocnew
  • 调用函数时,堆栈上没有剩余空间

两者实际上都与用于两者的AVR SRAM(Arduino上的2K)相同(除了静态数据,其大小在程序执行期间不会改变)。

通常,动态内存分配很少在MCU上使用,通常只有少数几个库使用它(其中一个是String类,您提到过您不使用它,这很不错)。

可以在下面的图片中看到堆栈和堆(由Adafruit提供): 在此处输入图片说明

因此,最令人期待的问题来自堆栈溢出(即,当堆栈向堆方向扩展并在堆上溢出时,然后-如果未完全使用堆-则在SRAM的静态数据区上发生了溢出。)您有以下任何一种的高风险:

  • 数据损坏(即堆栈覆盖堆或静态数据),使您难以理解
  • 堆栈损坏(即堆或静态数据覆盖堆栈内容),通常会导致崩溃

为了知道在堆的顶部和堆的顶部之间剩余的内存量(实际上,如果我们在如下图所示的同一图像上同时表示堆和堆,则可以将其称为底部)。可以使用以下功能:

int freeRam () {
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}

在上面的代码中,它__brkval指向堆的顶部,但指向0未使用堆的情况,在这种情况下,我们使用&__heap_start哪个指向__heap_start,它是第一个标记堆底部的变量;&v当然指向堆栈的顶部(这是最后压入堆栈的变量),因此上面的公式将返回可用于堆栈(或堆栈,如果使用的话)的可用内存量。

您可以在代码的各个位置使用此功能,以尝试找出此尺寸在何处急剧减小。

当然,如果您看到此函数返回负数,那就为时已晚:您已经溢出了堆栈!


1
主持人:很抱歉将此帖子发布到社区Wiki,在该帖子的中间键入时,我一定弄错了。请将此操作放回此处,因为此操作是无意的。谢谢。
jfpoilpret 2014年

感谢您提供此答案,我只是在短短一个小时前就找到了那段代码(在park.arduino.cc/Code/AvailableMemory#.UycOrueSxfg的底部)。我还没有包括它(但是我会),因为我的显示器上有很大的调试空间。我认为我一直对动态分配内容感到困惑。是malloc并且new我能做到的唯一方法吗?如果是这样,那么我就没有动态。另外,我刚刚了解到UNO具有2K的SRAM。我以为是1K。考虑到这些,我并没有用完RAM!我需要去别处。
Madivad

此外,还有calloc。但是您可能正在使用使用动态分配的第3方库,而却不知道(您必须检查所有依赖项的源代码才能确定)
jfpoilpret 2014年

2
有趣。唯一的“问题”是它在调用时报告了可用的RAM,因此,除非将其放在正确的位置,否则您可能不会注意到堆栈溢出。我提供的功能在该方面似乎具有优势,因为它可以报告到此为止的最小可用RAM,一旦使用了RAM地址,就不再报告可用空间了(在下面可能会占用一些RAM)与“ paint”值匹配的字节,并报告为空闲)。除此之外,根据用户的需求,一种方法可能比另一种方法更适合。
alexan_e 2014年

好点子!我没有在您的答案中注意到这一点(对我来说,实际上看起来像是一个错误),现在我看到了预先“绘画”自由区的要点。也许您可以在回答中更明确地说明这一点?
jfpoilpret 2014年

7

当您确定如何在临时目录中找到生成的.elf文件时,可以执行以下命令以转储SRAM使用情况,该project.elf位置将被生成的.elf文件替换。这个输出的优点是检查的能力如何您的SRAM使用。是否所有变量都必须是全局变量,是否确实需要所有变量?

avr-objdump -S -j .bss project.elf

project.elf:     file format elf32-avr


Disassembly of section .bss:

00800060 <__bss_start>:
        ...

00800070 <measurementReady>:
        ...

00800071 <cycles>:
        ...

00800073 <measurement>:
  800073:       00 00 00 00                                         ....

00800077 <measurementStart>:
  800077:       00 00 00 00                                         ....

0080007b <timerOverflows>:
  80007b:       00 00 00 00

请注意,这并未显示堆栈或动态内存的使用情况,如以下注释中所述的Ignacio Vazquez-Abrams所示。

另外avr-objdump -S -j .data project.elf可以检查a,但是我的程序都没有输出任何内容,因此我无法确定它是否有用。它应该列出 “已初始化(非零)数据”。


或者您可以使用avr-size。但这不会显示动态分配或堆栈使用情况。
伊格纳西奥·巴斯克斯

@ IgnacioVazquez-Abrams关于动力学,对于我的解决方案也是如此。编辑了我的答案。
jippie 2014年

好的,这是迄今为止最有趣的答案。我已经尝试过avr-objdumpavr-size并且稍后将在上面编辑我的帖子。谢谢你
Madivad 2014年

3

我怀疑有一段时间我将要用完可用的RAM。我不认为我使用了太多堆栈(尽管有可能),确定我实际使用多少RAM的最佳方法是什么?

最好结合使用手动估算和使用sizeof运算符。如果您的所有声明都是静态的,那么应该可以为您提供准确的描述。

如果使用动态分配,那么一旦开始取消分配内存,您可能会遇到问题。这是由于堆上的内存碎片。

经历并尝试解决它,当我遇到枚举和结构时会遇到问题;它们需要多少内存?

一个枚举与一个枚举占用一样多的空间int。因此,如果enum声明中有一组10个元素,则为10*sizeof(int)。此外,每个使用枚举的变量都只是一个int

对于结构,最容易使用sizeof来找出。结构占据的(最小)空间等于其成员的总和。如果编译器进行结构对齐,则可能会更多,但是在的情况下则不太可能avr-gcc


我会尽我所能静态分配所有内容。我从未想过sizeof为此目的使用。目前,我已经(全球)占了将近400个字节。现在,我将遍历并手动计算枚举和结构(其中有一些,我将使用sizeof),然后进行报告。
Madivad 2014年

不确定您是否真的需要sizeof知道静态数据的大小,因为该数据由avrdude IIRC打印。
jfpoilpret 2014年

@jfpoilpret我认为这取决于版本。并非所有版本和平台都提供该功能。我的(Linux,多个版本)不显示其中一个使用的内存,而Mac版本。
asheeshr 2014年

我搜索了详细的输出内容,我认为它应该在此处,而不是
Madivad 2014年

@AsheeshR我不知道,我的在Windows上运行良好。
jfpoilpret 2014年

1

有一个名为Arduino Builder的程序,可以清晰地显示程序正在使用的闪存,SRAM和EEPROM的数量。

Arduino的建设者

Arduino构建器构成CodeBlocks Arduino IDE解决方案的一部分。它可以用作独立程序,也可以通过CodeBlocks Arduino IDE使用。

不幸的Arduino Builder是有点,但它应该对大多数程序和最Arduinos,如欧诺工作。

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.