Sto usando troppa RAM. Come può essere misurato?


19

Vorrei sapere quanta RAM sto usando nel mio progetto, per quanto ne so, non c'è modo di risolverlo (se non quello di attraversarlo e calcolarlo da solo). Sono arrivato a un palcoscenico in un progetto piuttosto ampio in cui ho determinato che sto esaurendo la RAM.

Ho deciso questo perché posso aggiungere una sezione e poi si scatena l'inferno da qualche altra parte nel mio codice senza una ragione apparente. Se ho #ifndefqualcos'altro, funziona di nuovo. Non c'è nulla di programmaticamente sbagliato nel nuovo codice.

Ho sospettato per un po 'che stavo arrivando alla fine della RAM disponibile. Non penso di usare troppi stack (anche se è possibile), qual è il modo migliore per determinare quanta RAM sto effettivamente usando?

Passando attraverso e cercando di risolverlo, ho problemi quando arrivo a enumerazioni e strutture; quanta memoria costano?

prima modifica: INOLTRE, ho modificato il mio sketch così tanto dall'inizio, questi non sono i risultati effettivi che ho ottenuto inizialmente, ma sono quello che sto ottenendo ora.

  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 prima riga (con testo 17554) non funzionava, dopo molte modifiche, la seconda riga (con testo 16316) funziona come dovrebbe.

modifica: la terza riga ha tutto funzionante, lettura seriale, nuove funzioni, ecc. Ho sostanzialmente rimosso alcune variabili globali e un codice duplicato. Ne parlo perché (come sospettato) non si tratta di questo codice per sae, ma di utilizzo della RAM. Il che mi riporta alla domanda originale, "come misurarla al meglio" Sto ancora verificando alcune risposte, grazie.

Come interpreto effettivamente le informazioni di cui sopra?

Finora la mia comprensione è:

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

poiché BSS è considerevolmente inferiore a 1024 byte, perché il secondo funziona, ma il primo no? Se è DATA+BSSallora occupano entrambi più di 1024.

ri-modifica: ho modificato la domanda per includere il codice, ma ora l'ho rimosso perché in realtà non aveva nulla a che fare con il problema (a parte forse pratiche di codifica scadenti, dichiarazioni di variabili e simili). Puoi rivedere il codice guardando indietro attraverso le modifiche se vuoi davvero vederlo. Volevo tornare alla domanda a portata di mano, che era più basata su: come misurare l'utilizzo della RAM.


Ho pensato di aggiungere, ho aggiunto varie nuove sezioni di codice nelle ultime settimane, quindi l'ho optato per il funzionamento fino a quando non funziona, ma ora ho aggiunto solo mezza dozzina di byte e ho finito ... :(
Madivad

Usi il Stringtipo nei tuoi programmi? Questo è noto per eseguire frequenti allocazioni e rilasci di memoria dinamica, che possono frammentare l'heap fino al punto in cui non è possibile lasciare memroy.
jfpoilpret,

@jfpoilpret Sto lontano da Strings a causa del sovraccarico. Sono contento di lavorare con gli array di caratteri, detto questo, quasi sempre definisco tutti i miei array di caratteri con una dimensione fissa (al momento, ho una matrice di byte che non è puramente perché cambio la lunghezza del contenuto per differenti ricompilazioni.
Madivad

Pubblicare qui il tuo codice (o incollarlo se è troppo grande) potrebbe scoprire quali problemi hai riscontrato con la memoria.
jfpoilpret,

@jfpoilpret Non riesco davvero a pubblicare il codice, è enorme e purtroppo molto gonfio, distribuito su 16 file. Era un progetto che stavo permettendo di crescere ben oltre ciò che era richiesto (sono diversi progetti uniti). Sto iniziando ora a separarlo, che sono sicuro che aiuterà a risolvere il problema. Anche se ci sono alcune parti di cui ho bisogno che le persone guardino (o mi guidino), le posterò più avanti.
Madivad,

Risposte:


15

È possibile utilizzare le funzioni fornite da AVRGCC: Monitoraggio dell'utilizzo dello stack

La funzione aveva lo scopo di verificare l'utilizzo dello stack ma ciò che riporta è la RAM effettiva che non è mai stata utilizzata (durante l'esecuzione). Lo fa "dipingendo" (riempiendo) la RAM con un valore noto (0xC5), e quindi controllando l'area RAM contando quanti byte hanno ancora lo stesso valore iniziale.
Il rapporto mostrerà la RAM che non è stata utilizzata (RAM libera minima) e pertanto è possibile calcolare la RAM massima che è stata utilizzata (RAM totale - RAM segnalata).

Ci sono due funzioni:

  • StackPaint viene eseguito automaticamente durante l'inizializzazione e "dipinge" la RAM con il valore 0xC5 (può essere modificato se necessario).

  • StackCount può essere chiamato in qualsiasi momento per contare la RAM che non è stata utilizzata.

Ecco un esempio di utilizzo. Non fa molto ma ha lo scopo di mostrare come utilizzare le funzioni.

// -----------------------------------------------------------------------------
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);
}

pezzo interessante di codice, grazie. L'ho usato, e suggerisce che ci sono oltre 600 byte disponibili, ma quando seppellisco che nei sottotitoli più profondi si riduce, ma non cancella. Quindi FORSE il mio problema è altrove.
Madivad,

@Madivad Nota che questi oltre 600 byte rappresentano la quantità minima disponibile di RAM fino al punto in cui hai chiamato StackCount. In realtà non fa differenza quanto in profondità si effettua la chiamata, se la maggior parte del codice e delle chiamate nidificate sono state eseguite prima di chiamare StackCount, il risultato sarà corretto. Quindi, ad esempio, puoi lasciare la scheda in funzione per un po '(finché ci vuole abbastanza copertura del codice o idealmente fino a quando non ottieni il comportamento errato che descrivi) e quindi premi un pulsante per ottenere la RAM segnalata. Se ce n'è abbastanza, non è la causa del problema.
alexan_e

1
Grazie @alexan_e, ho creato un'area sul mio display che riporta questo ora, quindi mentre avanzi nei prossimi giorni guarderò questo numero con interesse, specialmente quando fallisce! Grazie ancora
Madivad

@Madivad Nota che la funzione fornita non riporterà i risultati corretti se nel codice viene utilizzato malloc ()
alexan_e

grazie per questo, lo so, è stato menzionato. Per quanto ne so, non lo sto usando (so che potrebbe esserci una libreria che lo utilizza, non ho ancora controllato completamente).
Madivad,

10

I problemi principali che puoi avere con l'utilizzo della memoria in fase di esecuzione sono:

  • nessuna memoria disponibile nell'heap per allocazioni dinamiche ( malloco new)
  • non rimane spazio nello stack quando si chiama una funzione

Entrambi sono in realtà gli stessi dell'AVR SRAM (2K su Arduino) utilizzato per entrambi (oltre ai dati statici la cui dimensione non cambia mai durante l'esecuzione del programma).

Generalmente, l'allocazione dinamica della memoria viene raramente utilizzata sulle MCU, solo poche librerie la usano in genere (una di queste è la Stringclasse, che hai citato di non usare, e questo è un buon punto).

La pila e il mucchio sono visibili nell'immagine seguente (per gentile concessione di Adafruit ): inserisci qui la descrizione dell'immagine

Quindi, il problema più atteso deriva dallo overflow dello stack (ovvero quando lo stack cresce verso l'heap e trabocca su di esso, e quindi, se l'heap non è stato utilizzato a tutti, overflow nell'area dati statici della SRAM. A quel tempo, hai un alto rischio di:

  • corruzione dei dati (ad es. lo stack ovewrites dati heap o statici), offrendo un comportamento incomprensibile
  • corruzione dello stack (ovvero l'heap o i dati statici sovrascrivono il contenuto dello stack), generando generalmente un arresto anomalo

Per conoscere la quantità di memoria rimasta tra la parte superiore dell'heap e la parte superiore dello stack (in realtà, potremmo chiamarlo il fondo se rappresentiamo sia l'heap che lo stack sulla stessa immagine mostrata di seguito), tu può usare la seguente funzione:

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

Nel codice sopra, __brkvalpunta nella parte superiore dell'heap ma è 0quando l'heap non è stato utilizzato, nel qual caso utilizziamo &__heap_startquali punti __heap_start, la prima variabile che segna la parte inferiore dell'heap; &vpunta ovviamente in cima allo stack (questa è l'ultima variabile inserita nello stack), quindi la formula sopra restituisce la quantità di memoria disponibile per lo stack (o l'heap se lo usi) per crescere.

Puoi utilizzare questa funzione in varie posizioni del codice per provare a scoprire dove questa dimensione si sta riducendo drasticamente.

Naturalmente, se mai vedi questa funzione restituire un numero negativo, allora è troppo tardi: hai già superato lo stack!


1
Ai moderatori: scusate per aver messo questo post nella wiki della community, devo aver fatto qualcosa di sbagliato durante la digitazione, nel mezzo del post. Ti preghiamo di rimetterlo qui poiché questa azione non è stata intenzionale. Grazie.
jfpoilpret,

grazie per questa risposta, ho letteralmente trovato SOLO quel pezzo di codice appena un'ora fa (in fondo a playground.arduino.cc/Code/AvailableMemory#.UycOrueSxfg ). Non l'ho ancora incluso (ma lo farò) poiché ho un'area abbastanza ampia per il debug sul mio display. Penso di essere stato confuso riguardo all'assegnazione dinamica delle cose. È malloce newl'unico modo in cui posso farlo? Se è così, allora non ho nulla di dinamico. Inoltre, ho appena imparato che UNO ha 2K di SRAM. Ho pensato che fosse 1K. Tenendo conto di questi, non sto esaurendo la RAM! Devo cercare altrove.
Madivad,

Inoltre, c'è anche calloc. Ma potresti usare librerie di terze parti che usano l'allocazione dinamica a tua insaputa (dovresti controllare il codice sorgente di tutte le tue dipendenze per esserne sicuro)
jfpoilpret

2
Interessante. L'unico "problema" è che riporta la RAM libera nel punto in cui viene chiamata, quindi a meno che non sia posizionata nella parte giusta potresti non notare un sovraccarico dello stack. La funzione che ho fornito sembra avere un vantaggio in quella zona poiché riporta la RAM libera minima fino a quel momento, una volta che è stato usato un indirizzo RAM, non è più riportato libero (sul lato inferiore potrebbe esserci della RAM occupata byte che corrispondono al valore "paint" e sono riportati come gratuiti). A parte questo, forse un modo si adatta meglio dell'altro a seconda di ciò che un utente desidera.
alexan_e

Buon punto! Non avevo notato questo punto specifico nella tua risposta (e per me che in effetti sembrava un bug), ora vedo il punto di "dipingere" in anticipo la zona libera. Forse potresti rendere questo punto più esplicito nella tua risposta?
jfpoilpret,

7

Quando scopri come individuare il file .elf generato nella tua directory temporanea, puoi eseguire il comando seguente per scaricare un utilizzo SRAM, dove project.elfdeve essere sostituito con il .elffile generato . Il vantaggio di questo output è la capacità di controllare come viene utilizzata la SRAM. Tutte le variabili devono essere globali, sono davvero tutte necessarie?

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

Si noti che ciò non mostra l'utilizzo dello stack o della memoria dinamica, come osservato da Ignacio Vazquez-Abrams nei commenti seguenti.

Inoltre a avr-objdump -S -j .data project.elfpuò essere verificato, ma nessuno dei miei programmi produce nulla con quello, quindi non posso dire con certezza se sia utile. Si suppone che la lista 'inizializzato dati (non-zero)'.


O potresti semplicemente usare avr-size. Ma ciò non ti mostrerà allocazioni dinamiche o utilizzo dello stack.
Ignacio Vazquez-Abrams,

@ IgnacioVazquez-Abrams sulla dinamica, lo stesso per la mia soluzione. Modificato la mia risposta.
jippie,

Ok, questa è la risposta più interessante finora. Ho sperimentato avr-objdumpe avr-sizee modificherò il mio post sopra a breve. Grazie per questo.
Madivad,

3

Ho sospettato per un po 'che stavo arrivando alla fine della RAM disponibile. Non penso di usare troppi stack (anche se è possibile), qual è il modo migliore per determinare quanta RAM sto effettivamente usando?

Sarebbe meglio usare una combinazione di stima manuale e usando l' sizeofoperatore. Se tutte le tue dichiarazioni sono statiche, questo dovrebbe darti un quadro accurato.

Se si utilizzano allocazioni dinamiche, è possibile che si verifichi un problema quando si inizia a deallocare la memoria. Ciò è dovuto alla frammentazione della memoria sull'heap.

Passando attraverso e cercando di risolverlo, ho problemi quando arrivo a enumerazioni e strutture; quanta memoria costano?

Un enum occupa lo stesso spazio di un int. Quindi, se hai una serie di 10 elementi in una enumdichiarazione, sarebbe 10*sizeof(int). Inoltre, ogni variabile che utilizza un'enumerazione è semplicemente una int.

Per le strutture, sarebbe più facile da usare sizeofper scoprirlo. Le strutture occupano uno spazio (minimo) pari alla somma dei suoi membri. Se il compilatore struttura l'allineamento, potrebbe essere maggiore, tuttavia ciò è improbabile nel caso di avr-gcc.


Assegno staticamente tutto il più possibile. Non ho mai pensato di usarlo sizeofper questo scopo. Al momento, ho quasi 400 byte già contabilizzati (a livello globale). Ora esaminerò e calcolerò gli enum (manualmente) e le strutture (di cui ne ho alcuni - e userò sizeof), e riporterò indietro.
Madivad,

Non sei sicuro di dover veramente sizeofconoscere la dimensione dei tuoi dati statici poiché questo viene stampato da avrdude IIRC.
jfpoilpret,

@jfpoilpret Questo dipende dalla versione, credo. Non tutte le versioni e piattaforme lo forniscono. Il mio (Linux, versioni multiple) non mostra l'uso della memoria per uno, mentre le versioni per Mac lo fanno.
asheeshr,

Ho cercato l'output dettagliato, ho pensato che dovrebbe essere lì, non lo è
Madivad

@AsheeshR Non ne ero a conoscenza, il mio funziona benissimo su Windows.
jfpoilpret,

1

Esiste un programma chiamato Arduino Builder che fornisce una visualizzazione accurata della quantità di flash, SRAM ed EEPROM utilizzata dal programma.

Arduino Builder

Il builder Arduino fa parte della soluzione IDE Arduino CodeBlocks . Può essere utilizzato come programma autonomo o tramite l'IDE Arduino CodeBlocks.

Sfortunatamente Arduino Builder è un po ' vecchio ma dovrebbe funzionare per la maggior parte dei programmi e la maggior parte degli Arduinos, come Uno.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.