Estoy usando demasiada RAM. ¿Cómo se puede medir esto?

19

Me gustaría saber cuánta RAM estoy usando en mi proyecto, por lo que puedo decir, no hay forma de resolverlo (aparte de calcularlo yo mismo). Llegué a una etapa en un proyecto bastante grande donde he determinado que me estoy quedando sin RAM.

He determinado esto porque puedo agregar una sección y luego todo el infierno se desata en otro lugar en mi código sin razón aparente. Si salgo #ifndefalgo más, funciona de nuevo. No hay nada programáticamente incorrecto con el nuevo código.

Sospeché por un tiempo que estaba llegando al final de la RAM disponible. No creo que esté usando demasiada pila (aunque es posible), ¿cuál es la mejor manera de determinar cuánta RAM estoy usando realmente?

Revisando e intentando resolverlo, tengo problemas cuando llego a enumeraciones y estructuras; ¿Cuánta memoria cuestan?

primera edición: TAMBIÉN, he editado mucho mi boceto desde que comencé, estos no son los resultados reales que obtuve inicialmente, pero son lo que estoy obteniendo ahora.

  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

La primera línea (con el texto 17554) no funcionaba, después de mucha edición, la segunda línea (con el texto 16316) funciona como debería.

editar: la tercera línea tiene todo funcionando, lectura en serie, mis nuevas funciones, etc. Básicamente eliminé algunas variables globales y código duplicado. Menciono esto porque (como se sospecha) no se trata de este código en sí, sino del uso de RAM. Lo que me lleva de vuelta a la pregunta original, "cómo medirla mejor" Todavía estoy revisando algunas respuestas, gracias.

¿Cómo interpreto realmente la información anterior?

Hasta ahora mi entendimiento es:

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

dado que BSS es considerablemente menor que 1024 bytes, ¿por qué funciona el segundo, pero el primero no? Si es así, DATA+BSSambos ocupan más de 1024.

reeditar: edité la pregunta para incluir el código, pero ahora la eliminé porque realmente no tenía nada que ver con el problema (aparte de quizás prácticas de codificación deficientes, declaraciones de variables y similares). Puede revisar el código al revisar las ediciones si realmente desea verlo. Quería volver a la pregunta en cuestión, que estaba más basada en: Cómo medir el uso de RAM.

Madivad
fuente
Pensé en agregar, agregué varias secciones nuevas de código en las últimas semanas, luego lo opté por hasta que funcione, pero ahora solo he agregado media doz byte vars y he terminado ... :(
Madivad
¿Usas Stringescribir en tus programas? Se sabe que esto realiza asignaciones y lanzamientos frecuentes de memoria dinámica, que pueden fragmentar el montón hasta el punto en que no queda memoria.
jfpoilpret
@jfpoilpret Me mantengo alejado de Strings debido a la sobrecarga. Estoy trabajando feliz con arrays de char, dicho esto, casi siempre me defino todos mis arreglos de char con un tamaño fijo (por el momento, tengo una matriz de bytes que no es puramente debido a que cambie la longitud del contenido para diferentes recompilaciones.
Madivad
Publicar su código aquí (o para pegar si es demasiado grande) puede ayudar a descubrir qué problemas encuentra con la memoria.
jfpoilpret
@jfpoilpret Realmente no puedo publicar el código, es enorme y desafortunadamente muy hinchado, distribuido en 16 archivos. Era un proyecto que permitía crecer mucho más allá de lo requerido (son varios proyectos fusionados). Estoy comenzando ahora a separarlo, lo que estoy seguro ayudará a solucionar el problema. Aunque hay algunas partes que necesito que la gente vea (o me guíe), las publicaré más tarde.
Madivad

Respuestas:

15

Puede usar las funciones proporcionadas AVRGCC: Monitoreo del uso de la pila

La función estaba destinada a verificar el uso de la pila, pero lo que informa es la RAM real que nunca se ha utilizado (durante la ejecución). Lo hace "pintando" (llenando) la RAM con un valor conocido (0xC5), y luego verificando el área de RAM contando cuántos bytes todavía tienen el mismo valor inicial.
El informe mostrará la RAM que no se ha utilizado (RAM libre mínima) y, por lo tanto, puede calcular la RAM máxima que se ha utilizado (RAM total - RAM informada).

Hay dos funciones:

  • StackPaint se ejecuta automáticamente durante la inicialización y "pinta" la RAM con el valor 0xC5 (se puede cambiar si es necesario).

  • Se puede llamar a StackCount en cualquier momento para contar la RAM que no se ha utilizado.

Aquí hay un ejemplo de uso. No hace mucho, pero tiene la intención de mostrar cómo usar las funciones.

// -----------------------------------------------------------------------------
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);
}
alexan_e
fuente
interesante pieza de código, gracias. Lo utilicé y sugiere que hay más de 600 bytes disponibles, pero cuando lo entierro en los subs más profundos se reduce, pero no se borra. Quizás mi problema esté en otra parte.
Madivad
@Madivad Tenga en cuenta que estos más de 600 bytes representan la cantidad mínima de RAM disponible hasta el punto en que llamó a StackCount. Realmente no importa la profundidad con la que realice la llamada, si la mayoría del código y las llamadas anidadas se han ejecutado antes de llamar a StackCount, el resultado será correcto. Entonces, por ejemplo, puede dejar su placa funcionando por un tiempo (siempre que sea necesario para obtener suficiente cobertura de código o idealmente hasta que obtenga el mal comportamiento que describe) y luego presione un botón para obtener la RAM informada. Si hay suficiente, entonces no es la causa del problema.
alexan_e
1
Gracias @alexan_e, he creado un área en mi pantalla que informa esto ahora, así que a medida que avance en los próximos días veré este número con interés, ¡especialmente cuando falla! Gracias de nuevo
Madivad
@Madivad Tenga en cuenta que la función dada no informará los resultados correctos si se usa malloc () en el código
alexan_e
gracias por eso, lo sé, se ha mencionado. Por lo que sé, no lo estoy usando (sé que puede haber una biblioteca que lo esté usando, aún no lo he verificado completamente).
Madivad
10

Los principales problemas que puede tener con el uso de memoria en tiempo de ejecución son:

  • no hay memoria disponible en el montón para asignaciones dinámicas ( malloco new)
  • no queda espacio en la pila cuando se llama a una función

Ambos son en realidad lo mismo que el AVR SRAM (2K en Arduino) se usa para ambos (además de los datos estáticos cuyo tamaño nunca cambia durante la ejecución del programa).

En general, la asignación de memoria dinámica rara vez se usa en MCU, solo unas pocas bibliotecas suelen usarla (una de ellas es la Stringclase, que mencionó que no usa, y ese es un buen punto).

La pila y el montón se pueden ver en la imagen a continuación (cortesía de Adafruit ): ingrese la descripción de la imagen aquí

Por lo tanto, el problema más esperado proviene del desbordamiento de la pila (es decir, cuando la pila crece hacia el montón y se desborda en él, y luego, si el montón no se usó en absoluto, se desborda en la zona de datos estáticos de la SRAM. En ese momento, tiene un alto riesgo de:

  • corrupción de datos (es decir, la pila sobrescribe datos de montón o estáticos), lo que le proporciona un comportamiento incomprensible
  • corrupción de la pila (es decir, el montón o los datos estáticos sobrescriben el contenido de la pila), lo que generalmente provoca un bloqueo

Para saber la cantidad de memoria que queda entre la parte superior del montón y la parte superior de la pila (en realidad, podríamos llamarla la parte inferior si representamos tanto el montón como la pila en la misma imagen que se muestra a continuación), puede usar la siguiente función:

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

En el código anterior, __brkvalapunta a la parte superior del montón pero es 0cuando el montón no se ha utilizado, en cuyo caso usamos &__heap_startqué puntos __heap_start, la primera variable que marca la parte inferior del montón; &vpuntos, por supuesto, a la parte superior de la pila (esta es la última variable introducida en la pila), por lo tanto, la fórmula anterior devuelve la cantidad de memoria disponible para la pila (o el montón si la usa) para crecer.

Puede usar esta función en varias ubicaciones de su código para intentar descubrir dónde se reduce drásticamente este tamaño.

Por supuesto, si alguna vez ve que esta función devuelve un número negativo, es demasiado tarde: ¡ya ha desbordado la pila!

revs jfpoilpret
fuente
1
Para los moderadores: perdón por poner esta publicación en el wiki de la comunidad, debo haber hecho algo mal al escribir, en el medio de la publicación. Vuelva a colocarlo aquí ya que esta acción no fue intencional. Gracias.
jfpoilpret
gracias por esta respuesta, literalmente solo encontré ese fragmento de código hace apenas una hora (en la parte inferior de playground.arduino.cc/Code/AvailableMemory#.UycOrueSxfg ). Todavía no lo he incluido (pero lo haré) ya que tengo un área bastante grande para depurar en mi pantalla. Creo que he estado confundido sobre la asignación dinámica de cosas. ¿Es mallocy newla única forma en que puedo hacer eso? Si es así, entonces no tengo nada dinámico. Además, acabo de enterarme de que UNO tiene 2K de SRAM. Pensé que era 1K. Teniendo esto en cuenta, ¡no me estoy quedando sin RAM! Necesito buscar en otro lado.
Madivad
Además, también hay calloc. Pero puede estar usando librerías de terceros que usan la asignación dinámica sin que usted lo sepa (tendría que verificar el código fuente de todas sus dependencias para estar seguro)
jfpoilpret
2
Interesante. El único "problema" es que informa la RAM libre en el punto donde se llama, por lo que, a menos que se coloque en la parte correcta, es posible que no note un desbordamiento de la pila. La función que he proporcionado parece tener una ventaja en esa área ya que informa la RAM libre mínima hasta ese punto, una vez que se ha utilizado una dirección RAM, ya no se informa libre (en el lado negativo puede haber algo de RAM ocupada). bytes que coinciden con el valor de "pintura" y se informan como libres). Aparte de eso, tal vez una forma se adapte mejor que la otra dependiendo de lo que un usuario quiera.
alexan_e
¡Buen punto! No había notado este punto específico en su respuesta (y para mí eso parecía un error, de hecho), ahora veo el punto de "pintar" la zona libre por adelantado. ¿Quizás podría hacer este punto más explícito en su respuesta?
jfpoilpret
7

Cuando descubra cómo ubicar el archivo .elf generado en su directorio temporal, puede ejecutar el siguiente comando para volcar un uso de SRAM, donde project.elfse reemplazará con el .elfarchivo generado . La ventaja de esta salida es la capacidad de inspeccionar cómo se usa su SRAM. ¿Todas las variables deben ser globales, son realmente necesarias?

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

Tenga en cuenta que esto no muestra el uso de la pila o la memoria dinámica como lo señaló Ignacio Vázquez-Abrams en los comentarios a continuación.

Además, avr-objdump -S -j .data project.elfse puede verificar a, pero ninguno de mis programas genera nada con eso, así que no puedo decir con certeza si es útil. Se supone que enumera 'datos inicializados (no cero)'.

jippie
fuente
O simplemente podrías usarlo avr-size. Pero eso no le mostrará las asignaciones dinámicas o el uso de la pila.
Ignacio Vazquez-Abrams
@ IgnacioVazquez-Abrams sobre la dinámica, lo mismo para mi solución. Edité mi respuesta.
jippie
Ok, esta es la respuesta más interesante hasta ahora. He experimentado con avr-objdumpy avr-sizeeditaré mi publicación anterior en breve. Gracias por esto.
Madivad
3

Sospeché por un tiempo que estaba llegando al final de la RAM disponible. No creo que esté usando demasiada pila (aunque es posible), ¿cuál es la mejor manera de determinar cuánta RAM estoy usando realmente?

Sería mejor utilizar una combinación de estimación manual y utilizando el sizeofoperador. Si todas sus declaraciones son estáticas, esto debería proporcionarle una imagen precisa.

Si está utilizando asignaciones dinámicas, puede encontrarse con un problema una vez que comience a desasignar la memoria. Esto se debe a la fragmentación de la memoria en el montón.

Revisando e intentando resolverlo, tengo problemas cuando llego a enumeraciones y estructuras; ¿Cuánta memoria cuestan?

Una enumeración ocupa tanto espacio como una int. Entonces, si tiene un conjunto de 10 elementos en una enumdeclaración, sería 10*sizeof(int). Además, cada variable que usa una enumeración es simplemente un int.

Para las estructuras, sería más fácil de usar sizeofpara averiguarlo. Las estructuras ocupan un espacio (mínimo) igual a la suma de sus miembros. Si el compilador estructura la alineación, entonces puede ser más, sin embargo, esto es poco probable en el caso de avr-gcc.

Asheeshr
fuente
Asigno estáticamente todo lo que puedo. Nunca pensé en usar sizeofpara este propósito. Por el momento, tengo casi 400 bytes ya representados (globalmente). Ahora revisaré y calcularé las enumeraciones (manualmente) y las estructuras (de las cuales tengo algunas, y las usaré sizeof), e informaré de nuevo.
Madivad
No estoy seguro de que realmente necesite sizeofsaber el tamaño de sus datos estáticos, ya que esto es impreso por avrdude IIRC.
jfpoilpret
@jfpoilpret Eso depende de la versión, creo. No todas las versiones y plataformas proporcionan eso. El mío (Linux, varias versiones) no muestra el uso de memoria para uno, mientras que las versiones de Mac sí.
asheeshr
He buscado la salida detallada, pensé que debería estar allí, no lo está
Madivad
@AsheeshR No estaba al tanto de eso, el mío funciona bien en Windows.
jfpoilpret
1

Hay un programa llamado Arduino Builder que proporciona una visualización clara de la cantidad de flash, SRAM y EEPROM que está utilizando su programa.

Constructor Arduino

El constructor Arduino forma parte de la solución CodeBlocks Arduino IDE . Se puede utilizar como un programa independiente o a través del IDE de CodeBlocks Arduino.

Desafortunadamente, Arduino Builder es un poco viejo, pero debería funcionar para la mayoría de los programas y la mayoría de los Arduinos, como el Uno.

sa_leinad
fuente