Ich benutze zu viel RAM. Wie kann das gemessen werden?

19

Ich würde gerne wissen, wie viel RAM ich in meinem Projekt verwende, soweit ich das beurteilen kann. Ich habe eine Phase in einem ziemlich großen Projekt erreicht, in der ich festgestellt habe, dass mir der Arbeitsspeicher ausgeht.

Ich habe dies festgestellt, weil ich einen Abschnitt hinzufügen kann und dann irgendwo anders in meinem Code die Hölle losbricht, ohne dass es einen offensichtlichen Grund gibt. Wenn ich #ifndefwas anderes raus habe, klappt es wieder. An dem neuen Code ist programmgesteuert nichts auszusetzen.

Ich vermutete für eine Weile, dass ich das Ende des verfügbaren Arbeitsspeichers erreichte. Ich glaube nicht, dass ich zu viel Stack verwende (obwohl dies möglich ist). Wie kann ich am besten feststellen, wie viel RAM ich tatsächlich verwende?

Beim Durcharbeiten und Ausarbeiten habe ich Probleme, wenn ich an Aufzählungen und Strukturen komme. Wie viel Speicher kosten sie?

zuerst bearbeiten: Auch ich habe meine Skizze so viel geändert seit dem Start, das sind nicht die tatsächlichen Ergebnisse , die ich am Anfang bekam, aber sie sind das, was ich jetzt bekommen.

  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

Die erste Zeile (mit Text 17554) funktionierte nicht, nach langem Bearbeiten funktioniert die zweite Zeile (mit Text 16316) ordnungsgemäß.

Bearbeiten: In der dritten Zeile funktioniert alles, serielles Lesen, meine neuen Funktionen usw. Ich habe im Wesentlichen einige globale Variablen und doppelten Code entfernt. Ich erwähne dies, weil es (wie vermutet) nicht um diesen Code per sae geht, sondern um die RAM-Auslastung. Das bringt mich zurück zu der ursprünglichen Frage, wie man es am besten misst. Ich überprüfe immer noch einige Antworten, danke.

Wie interpretiere ich die obigen Informationen tatsächlich?

Bisher ist mein Verständnis:

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

Warum funktioniert das zweite, das erste jedoch nicht? Wenn es DATA+BSSdann ist, besetzen beide mehr als 1024.

Erneut bearbeiten: Ich habe die Frage bearbeitet, um den Code einzuschließen, aber jetzt habe ich sie entfernt, da sie wirklich nichts mit dem Problem zu tun hatte (abgesehen von möglicherweise schlechten Codierungspraktiken, Variablendeklarationen und dergleichen). Sie können den Code überprüfen, indem Sie sich die Änderungen noch einmal ansehen, wenn Sie ihn wirklich sehen möchten. Ich wollte noch einmal auf die Frage zurückkommen, die sich eher mit der Messung der RAM-Auslastung befasst.

Madivad
quelle
Ich dachte, ich würde hinzufügen, ich habe in den letzten Wochen verschiedene neue Codeabschnitte hinzugefügt und sie dann optimiert, bis es funktioniert, aber jetzt habe ich nur noch ein halbes Dutzend Byte hinzugefügt und ich bin fertig ... :(
Madivad
Verwenden Sie StringTyp in Ihren Programmen? Dies führt bekanntermaßen zu häufigen dynamischen Speicherzuweisungen und -freigaben, wodurch der Heap möglicherweise so fragmentiert wird, dass möglicherweise kein Speicher mehr vorhanden ist.
jfpoilpret
@jfpoilpret Ich halte mich Stringwegen des Overheads von s fern . Ich arbeite gerne mit char-Arrays, das heißt, ich definiere fast immer alle char-Arrays mit einer festen Größe (im Moment habe ich EIN Byte-Array, das nicht nur darauf beruht, dass ich die Inhaltslänge für verschiedene Neukompilierungen ändere.
Madivad
Wenn Sie Ihren Code hier veröffentlichen (oder in den Pastebin, wenn er zu groß ist), können Sie möglicherweise herausfinden, auf welche Probleme Sie mit dem Speicher stoßen.
jfpoilpret
@jfpoilpret Ich kann den Code nicht wirklich posten, er ist riesig und leider sehr aufgebläht und auf 16 Dateien verteilt. Es war ein Projekt, das ich weit über das hinaus wachsen ließ, was erforderlich war (es sind mehrere Projekte, die zusammengeführt wurden). Ich fange jetzt an, es auseinander zu brechen, was sicher helfen wird, das Problem zu beheben. Obwohl es einige Teile davon gibt, die ich anschauen (oder weiterleiten) muss, werde ich diese später veröffentlichen.
Madivad

Antworten:

15

Sie können die Funktionen von AVRGCC: Monitoring Stack Usage verwenden

Die Funktion sollte die Stack-Auslastung überprüfen, meldet jedoch, dass der tatsächliche RAM-Speicher (während der Ausführung) noch nie verwendet wurde. Dies geschieht, indem der RAM mit einem bekannten Wert (0xC5) "gemalt" (gefüllt) wird und dann der RAM-Bereich überprüft wird, der zählt, wie viele Bytes noch den gleichen Anfangswert haben.
In dem Bericht wird der nicht verwendete Arbeitsspeicher angezeigt (minimaler freier Arbeitsspeicher). Daher können Sie den maximal verwendeten Arbeitsspeicher berechnen (Gesamter Arbeitsspeicher - gemeldeter Arbeitsspeicher).

Es gibt zwei Funktionen:

  • StackPaint wird während der Initialisierung automatisch ausgeführt und "malt" den RAM mit dem Wert 0xC5 (kann bei Bedarf geändert werden).

  • StackCount kann jederzeit aufgerufen werden, um den nicht verwendeten RAM zu zählen.

Hier ist ein Anwendungsbeispiel. Macht nicht viel, soll aber zeigen, wie man die Funktionen benutzt.

// -----------------------------------------------------------------------------
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
quelle
interessanter Code, danke. Ich habe es benutzt, und es deutet darauf hin, dass 600+ Bytes verfügbar sind, aber wenn ich begrabe, dass es in den tieferen Subs zwar abnimmt, aber nicht auslöscht. Also ist mein Problem vielleicht woanders.
Madivad
@Madivad Beachten Sie, dass diese 600+ Bytes die minimal verfügbare RAM-Größe bis zu dem Zeitpunkt darstellen, an dem Sie StackCount aufgerufen haben. Es spielt keine Rolle, wie tief Sie den Aufruf vertiefen. Wenn der Großteil des Codes und der verschachtelten Aufrufe vor dem Aufruf von StackCount ausgeführt wurden, ist das Ergebnis korrekt. So können Sie zum Beispiel Ihr Board eine Weile arbeiten lassen (solange es dauert, bis eine ausreichende Codeabdeckung vorliegt, oder im Idealfall, bis Sie das von Ihnen beschriebene Fehlverhalten feststellen) und dann einen Knopf drücken, um den gemeldeten RAM-Speicher abzurufen. Wenn es genug gibt, ist es nicht die Ursache des Problems.
Alexander_e
1
Danke @alexan_e, ich habe einen Bereich auf meinem Display erstellt, der dies jetzt meldet. Wenn ich in den nächsten Tagen Fortschritte mache, schaue ich mir diese Nummer mit Interesse an, besonders wenn sie fehlschlägt!
Nochmals vielen
@Madivad Bitte beachten Sie, dass die angegebene Funktion keine korrekten Ergebnisse liefert, wenn malloc () im Code verwendet wird
alexan_e
danke dafür, ich bin mir bewusst, es wurde erwähnt. Soweit mir bekannt ist, verwende ich es nicht (ich weiß, dass es möglicherweise eine Bibliothek gibt, die es verwendet, ich habe es noch nicht vollständig überprüft).
Madivad
10

Die Hauptprobleme, die Sie mit der Speichernutzung zur Laufzeit haben können, sind:

  • Kein verfügbarer Speicher im Heap für dynamische Zuordnungen ( mallocoder new)
  • Beim Aufruf einer Funktion ist kein Platz mehr auf dem Stapel

Beide sind tatsächlich identisch mit dem AVR SRAM (2K auf Arduino), der für beide verwendet wird (zusätzlich zu statischen Daten, deren Größe sich während der Programmausführung nie ändert).

Im Allgemeinen wird die dynamische Speicherzuweisung auf MCUs selten verwendet. In der Regel wird sie nur von wenigen Bibliotheken verwendet (eine davon ist Stringclass, von der Sie sagten, dass Sie sie nicht verwenden, und das ist ein guter Punkt).

Der Stapel und der Haufen sind im Bild unten zu sehen (mit freundlicher Genehmigung von Adafruit ): Bildbeschreibung hier eingeben

Das am meisten erwartete Problem ist daher der Stapelüberlauf (dh, wenn der Stapel in Richtung des Heaps wächst und überläuft), und wenn der Heap nicht bei allen Überläufen in der statischen Datenzone des SRAM verwendet wurde. Sie haben ein hohes Risiko für:

  • Datenbeschädigung (dh der Stapel überschreibt den Heap oder statische Daten), was zu einem unverständlichen Verhalten führt
  • Stapelbeschädigung (dh der Heap oder die statischen Daten überschreiben den Stapelinhalt), was im Allgemeinen zu einem Absturz führt

Um zu wissen, wie viel Speicher zwischen der Oberseite des Heapspeichers und der Oberseite des Stapels verbleibt (wir können es auch die Unterseite nennen, wenn wir sowohl den Heapspeicher als auch den Stapel auf demselben Bild darstellen, wie unten dargestellt) kann die folgende Funktion verwenden:

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

In dem obigen Code, __brkvalweist auf die Spitze des Haufens ist aber , 0wenn der Haufen nicht verwendet worden ist, wobei in diesem Fall verwenden wir &__heap_startdie Punkte __heap_start, die erste Variable , dass markiert den Boden des Haufens; &vzeigt natürlich oben auf den Stapel (dies ist die letzte Variable, die auf den Stapel verschoben wurde), daher gibt die obige Formel die Menge an Speicher zurück, die für den Stapel (oder den Heap, wenn Sie ihn verwenden) zur Vergrößerung verfügbar ist.

Sie können diese Funktion an verschiedenen Stellen Ihres Codes verwenden, um herauszufinden, wo sich diese Größe dramatisch verringert.

Wenn diese Funktion jemals eine negative Zahl zurückgibt, ist es natürlich zu spät: Sie haben den Stapel bereits überschritten!

jfpoilpret
quelle
1
An die Moderatoren: Entschuldigen Sie, dass Sie diesen Beitrag in das Community-Wiki gestellt haben. Ich muss während des Tippens mitten im Beitrag etwas falsch gemacht haben. Bitte setzen Sie es hierher zurück, da diese Aktion ungewollt war. Vielen Dank.
jfpoilpret
danke für diese Antwort, ich habe buchstäblich erst vor einer knappen Stunde diesen Code gefunden (am Ende von playground.arduino.cc/Code/AvailableMemory#.UycOrueSxfg ). Ich habe es noch nicht aufgenommen (werde es aber tun), da ich einen ziemlich großen Bereich zum Debuggen auf meinem Display habe. Ich glaube, ich war verwirrt darüber, Dinge dynamisch zuzuweisen. Ist mallocund newder einzige Weg, wie ich das tun kann? Wenn ja, dann habe ich nichts dynamisches. Außerdem habe ich gerade erfahren, dass die UNO 2K SRAM hat. Ich dachte es wäre 1K. Unter Berücksichtigung dieser Faktoren geht mir nicht der Arbeitsspeicher aus! Ich muss woanders suchen.
Madivad
Darüber hinaus gibt es auch calloc. Möglicherweise verwenden Sie jedoch Bibliotheken von Drittanbietern, die die dynamische Zuordnung verwenden, ohne dass Sie dies wissen (Sie müssten den Quellcode aller Abhängigkeiten überprüfen, um sicherzugehen)
jfpoilpret
2
Interessant. Das einzige "Problem" besteht darin, dass der freie Arbeitsspeicher an der Stelle gemeldet wird, an der er aufgerufen wird. Wenn er sich nicht im richtigen Bereich befindet, wird möglicherweise kein Stapelüberlauf festgestellt. Die Funktion, die ich bereitgestellt habe, scheint in diesem Bereich einen Vorteil zu haben, da sie den min-freien RAM bis zu diesem Punkt meldet, sobald eine RAM-Adresse verwendet wurde, wird sie nicht mehr als frei gemeldet (auf der anderen Seite ist möglicherweise etwas RAM belegt Bytes, die mit dem Wert "paint" übereinstimmen und als frei gemeldet werden. Abgesehen davon passt vielleicht eine Möglichkeit besser als die andere, je nachdem, was ein Benutzer möchte.
alexan_e
Guter Punkt! Ich hatte diesen speziellen Punkt in Ihrer Antwort nicht bemerkt (und für mich sah das tatsächlich wie ein Fehler aus), jetzt sehe ich den Punkt, die freie Zone im Voraus zu "malen". Vielleicht könnten Sie diesen Punkt in Ihrer Antwort expliziter machen?
jfpoilpret
7

Wenn Sie herausfinden, wie Sie die generierte .elf-Datei in Ihrem temporären Verzeichnis finden, können Sie den folgenden Befehl ausführen, um eine SRAM-Verwendung zu sichern, bei project.elfder diese durch die generierte .elfDatei ersetzt werden soll. Der Vorteil dieser Ausgabe ist die Fähigkeit , zu überprüfen , wie Ihr SRAM verwendet wird. Müssen alle Variablen global sein, sind sie wirklich alle erforderlich?

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

Beachten Sie, dass dies keine Verwendung des Stacks oder des dynamischen Speichers anzeigt, wie Ignacio Vazquez-Abrams in den Kommentaren unten angegeben.

Zusätzlich avr-objdump -S -j .data project.elfkann ein geprüft werden, aber keines meiner Programme gibt etwas damit aus, so dass ich nicht sicher bin, ob es nützlich ist. Es sollte 'initialisierte (nicht null) Daten' auflisten.

jippie
quelle
Oder Sie könnten einfach verwenden avr-size. Das zeigt Ihnen jedoch keine dynamischen Zuordnungen oder Stapelverwendung.
Ignacio Vazquez-Abrams
@ IgnacioVazquez-Abrams über die Dynamik, selbe für meine Lösung. Bearbeitet meine Antwort.
jippie
Ok, das ist die bisher interessanteste Antwort. Ich habe mit avr-objdumpund experimentiert und avr-sizewerde meinen Beitrag in Kürze bearbeiten. Danke dafür.
Madivad
3

Ich vermutete für eine Weile, dass ich das Ende des verfügbaren Arbeitsspeichers erreichte. Ich glaube nicht, dass ich zu viel Stack verwende (obwohl dies möglich ist). Wie kann ich am besten feststellen, wie viel RAM ich tatsächlich verwende?

Es ist am besten, eine Kombination aus manueller Schätzung und Verwendung des sizeofOperators zu verwenden. Wenn alle Ihre Angaben statisch sind, sollte dies ein genaues Bild ergeben.

Wenn Sie dynamische Zuordnungen verwenden, tritt möglicherweise ein Problem auf, sobald Sie mit der Freigabe des Speichers beginnen. Dies liegt an der Speicherfragmentierung auf dem Heap.

Beim Durcharbeiten und Ausarbeiten habe ich Probleme, wenn ich an Aufzählungen und Strukturen komme. Wie viel Speicher kosten sie?

Eine Aufzählung nimmt so viel Platz ein wie eine int. Wenn Sie also einen Satz von 10 Elementen in einer enumDeklaration haben, wäre dies der Fall 10*sizeof(int). Außerdem ist jede Variable, die eine Aufzählung verwendet, einfach eine int.

Für Strukturen wäre es am einfachsten sizeof, dies herauszufinden. Strukturen nehmen einen (minimalen) Raum ein, der der Summe ihrer Mitglieder entspricht. Wenn der Compiler eine Strukturausrichtung durchführt, kann dies mehr sein, was jedoch im Fall von unwahrscheinlich ist avr-gcc.

Ascheshr
quelle
Ich ordne alles statisch zu, so weit ich kann. Ich hätte nie gedacht, sizeoffür diesen Zweck zu verwenden. Im Moment habe ich schon fast 400 Bytes (global) abgerechnet. Jetzt gehe ich durch und berechne die Aufzählungen (manuell) und die Strukturen (von denen ich einige habe - und ich werde sie verwenden sizeof) und erstatte Bericht.
Madivad
Sie sind sich nicht sicher, ob Sie sizeofdie Größe Ihrer statischen Daten wirklich kennen müssen , da diese von avrdude IIRC gedruckt werden.
jfpoilpret
@jfpoilpret Das ist versionsabhängig, denke ich. Nicht alle Versionen und Plattformen bieten dies. Meins (Linux, mehrere Versionen) zeigt keine Speicherbelegung für eine, während Mac-Versionen dies tun.
Asheeshr
Ich habe die ausführliche Ausgabe durchsucht, ich dachte, es sollte da sein, es ist nicht
Madivad
@AsheeshR Mir war das nicht bewusst, meins funktioniert einwandfrei unter Windows.
jfpoilpret
1

Es gibt ein Programm namens Arduino Builder , das eine übersichtliche Visualisierung der Menge an Flash, SRAM und EEPROM bietet, die Ihr Programm verwendet.

Arduino Builder

Der Arduino Builder ist Teil der CodeBlocks Arduino IDE- Lösung. Es kann entweder als eigenständiges Programm oder über die CodeBlocks Arduino IDE verwendet werden.

Leider ist Arduino Builder ein bisschen alt, aber es sollte für die meisten Programme und die meisten Arduinos, wie das Uno, funktionieren.

sa_leinad
quelle