Wie finde ich mit avr-objdump die großen Speicherfresser?
Problem:
>Globale Variablen verwenden 1537 Bytes (75%) des dynamischen Speichers, 511 Bytes
für lokale Variablen verbleiben. Das Maximum sind 2048 Bytes.
avr-objdump -t meinProgramm.ino.elf > map.txt
Wenn ich's richtig weiß, ist das RAM im .bss segment.
Muss man alles von Hand zusammenzählen?
Im RAM landen beim AVR .bss und .data. Das solltest du auch nicht ganz voll machen, da noch etwas für den Stack benötigt wird. In der vorletzten Spalte der objdump-Ausgabe ist doch die Größe des Objekts - da sieht man eigentlich schnell, wer viel braucht.
> Wie finde ich mit avr-objdump die großen Speicherfresser?
Einfacher isses in den Code zu schauen, i.d.R. suchst Du nämlich nicht
"die Großen" sondern vielviel Kleine. Zumindest bei atmegas dieser
Größe.
>Im RAM landen beim AVR .bss und .data. Das solltest du auch nicht ganz >voll machen, da noch etwas für den Stack benötigt wird. Das dachte ich mir schon. Bei ca. 200Byte Reserve fängt das Programm an, sich beim Start aufzuhängen. >In der vorletzten Spalte der objdump-Ausgabe ist doch die Größe des >Objekts - da sieht man eigentlich schnell, wer viel braucht. Das habe ich schon gesehen. Ich wollte es nur nicht manuell durchsuchen müssen. Was ist der Unterschied zwischen .bss und .data ?
g457 (Gast) >Einfacher isses in den Code zu schauen, i.d.R. suchst Du nämlich nicht >"die Großen" sondern vielviel Kleine. Zumindest bei atmegas dieser >Größe. Ja, es sind scheinbar viele kleine. Was mich ein wenig wundert ist, dass Objekte die noch nicht instantiiert sind und in einer Liste später gesammelt werden, schon ca. 10 Byte .data zu verbrauchen scheinen.
Bei über 1500 Byte an globalen Variablen sucht man nach großen Arrays. Diese Menge bekommt man mit vielen kleinen Variablen nicht mal annähernd zusammen. Oliver
chris schrieb: > Ich wollte es nur nicht manuell durchsuchen > müssen. Wie denn sonst? Du hast den Mist doch auch selbst da reingeschrieben.
chris schrieb: > Was mich ein wenig wundert ist, dass Objekte die noch nicht instantiiert > sind und in einer Liste später gesammelt werden, schon ca. 10 Byte .data > zu verbrauchen scheinen. Was meinst du mit "noch nicht instantiiert"? Globale Variablen sind immer da und brauchen immer Speicher. Es gibt kein "noch nicht instantiiert".
:
Bearbeitet durch User
Rolf M. schrieb: > Was meinst du mit "noch nicht instantiiert"? Der hat keine Ahnung und will hier auf wichtig machen.
chris schrieb: > Was ist der Unterschied zwischen .bss und .data ? .data sind initialisierte Variablen, also z.B. String Literals wie in printf("Test"); - sofern Du nicht dafür Sorge trägst, dass sie erst bei Bedarf vom Flash ins RAM kopiert werden. .bss sind nicht initialisierte Variablen, also z.B. globale Variablen ohne zugewiesenen Wert.
von Hmmm (Gast) >chris schrieb: >> Was ist der Unterschied zwischen .bss und .data ? >.data sind initialisierte Variablen, also z.B. String Literals wie in >printf("Test"); - sofern Du nicht dafür Sorge trägst, dass sie erst bei >Bedarf vom Flash ins RAM kopiert werden. >.bss sind nicht initialisierte Variablen, also z.B. globale Variablen >ohne zugewiesenen Wert. Danke dafür. Hier mal der Speicherverbrauch der Basisklasse "Component": 008003ba g O .data 00000010 .hidden _ZTV9Component Tatsächlich werden auch für jede abgeleitete Klasse 16 Bytes .data reserviert, obwohl kein einziges Objekt beim Programmstart instantiiert ist. Die Objekte werden erst während des Programmlaufs initialisiert. Es können 0 bis mehrere Objekte einer Klasse durch das Programm erzeugt werden. Hier die Zeile des Compiler-Laufs, um das Setup zu sehen: "/home/christoph/tools/arduino-1.8.5/hardware/tools/avr/bin/avr-g++" -c -g -Os -w -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -MMD -flto -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10805 -DARDUINO_AVR_NANO -DARDUINO_ARCH_AVR "-I/home/christoph/tools/arduino-1.8.5/hardware/arduino/avr/cores/arduin o" "-I/home/christoph/tools/arduino-1.8.5/hardware/arduino/avr/variants/eig htanaloginputs" "-I/home/christoph/tools/arduino-1.8.5/hardware/arduino/avr/libraries/EE PROM/src" "-I/home/christoph/Arduino/libraries/MemoryFree" "/tmp/arduino_build_683962/sketch/Component.cpp" -o "/tmp/arduino_build_683962/sketch/Component.cpp.o" Warum verbrauchen die Klassen 16Bytes an initialiertem .data-RAM, ohne ein einziges Objekt?
chris schrieb: > Warum verbrauchen die Klassen 16Bytes an initialiertem .data-RAM, ohne > ein einziges Objekt? Das liegt bei AVR an der vtable, die der gcc blöderweise ins RAM packt. Jede virtuelle Elementfunktion benötigt darin dann 2 Byte zzgl. der bei Null-Einträge am Anfang sowie dem virtuellen dtor.
>Das liegt bei AVR an der vtable,
Danke dafür, so was habe ich schon vermutet.
Am Anfang des Projektes habe ich noch überlegt, ob ich es in C machen
soll und die Klassen von Hand via Strukturen basteln. Es scheint mir,
das wäre der bessere Weg gewesen.
Ich habe 30 Klassen a 16Bytes was mich ca. ein viertel des Speichers
kostet.
chris schrieb: > Es scheint mir, > das wäre der bessere Weg gewesen. Die Frage ist, ob Du überhaupt Laufzeitpolymorphie benötigst. Meistens ist das nämlich gar nicht der Fall und man kann das durch statische Polymorphie ersetzen.
chris schrieb: > Ich habe 30 Klassen a 16Bytes was mich ca. ein viertel des Speichers > kostet. Was allerdings bedeutet, daß nichtmal eine Instanz von jeder Klasse ins RAM passt. Das Konzept erscheint nicht ganz durchdacht. Oliver
Beitrag #6164574 wurde von einem Moderator gelöscht.
Folgendes Tool könnte vielleicht nützlich sein: http://www.sikorskiy.net/prj/amap/ "amap : A tool to analyze .MAP files produced by 32/64-bit Visual Studio compiler and report the amount of memory being used by data and code. This app can also read and analyze MAP files produced by the GCC, Xbox360, Wii, PS3 (gcc and SNC), and PS4 compilers." Das erleichtert zumindest das Suchen etwas...
Aaaaahhhhhh ... Objekte auf einem AVR ... aaaaahhh. Man kann auchb wirklich alles falsch machen. Was sollen die denn bringen ? Eine linked list ? Sollte man immer alles statisch allozieren.
Joggel E. schrieb: > Aaaaahhhhhh ... Objekte auf einem AVR ... aaaaahhh. > Man kann auchb wirklich alles falsch machen. Was hast Du gegen Objekte? Du meinst virtuelle Elementfunktionen und Laufzeitpolymorphie.
Hi https://github.com/ARMmbed/mbed-os-linker-report Ganz toll um sich einen Überblick zu verschaffen. Matthias
chris schrieb: > Wie finde ich mit avr-objdump die großen Speicherfresser? Ist das Projekt geheim, oder kannst du das hier mal hochladen? Oliver
>> Joggel E. schrieb: >> Aaaaahhhhhh ... Objekte auf einem AVR ... aaaaahhh. >> Man kann auchb wirklich alles falsch machen. >Was hast Du gegen Objekte? > >Du meinst virtuelle Elementfunktionen und Laufzeitpolymorphie. Ja, ich habe etwas gegen Objekte auf Controllern. Was sollen die bringen ?
Wilhelm M. schrieb: > Joggel E. schrieb: >> Aaaaahhhhhh ... Objekte auf einem AVR ... aaaaahhh. >> Man kann auchb wirklich alles falsch machen. > > Was hast Du gegen Objekte? Vermutlich hatte er gerade einen Schlaganfall. Joggel E. schrieb: > Ja, ich habe etwas gegen Objekte auf Controllern. > > Was sollen die bringen ? Sauberen Code? Gegenfrage, wo siehst du das Problem darin, wenn man sie richtig verwendet?
Joggel E. schrieb: > Ja, ich habe etwas gegen Objekte auf Controllern.
1 | int x{}; |
2 | |
3 | A y{}; |
Hier hast Du ZWEI Objekte. Und jetzt erkläre, was daran verwerflich sein soll.
Vincent H. schrieb: > Folgendes Tool könnte vielleicht nützlich sein: > http://www.sikorskiy.net/prj/amap/ Stimmt. Gerade ausprobiert und jetzt in meine Werkzeugsammung aufgenommen. Danke für den Tipp!
Textest Du die Leute voll? Bei Textvariablen wird diese in's RAM kopiert, was mal schnell ein paar Bytes verbraucht. Also: Texte explizit im Flash ablegen. Ansonsten fällt mir nur noch ein: So viele Variablen, wie möglich, lokal, in den Funktionen anlegen. Da brauchst Du kein malloc und so'n Kram und das Recycling passiert beim Rücksprung.
:
Bearbeitet durch User
Sebastian S. schrieb: > Ansonsten fällt mir nur noch ein: > So viele Variablen, wie möglich, lokal, in den Funktionen anlegen. Da > brauchst Du kein malloc und so'n Kram und das Recycling passiert beim > Rücksprung. Es hat aber auch einen großen Nachteil: Es ist zur Compilezeit nicht überschaubar, wie viel RAM man tatsächlich braucht. Und wenn die Daten zu groß für den Speicher sind, gibt's einfach nur fehlerhaftes Verhalten.
@Rolf M. >Es hat aber auch einen großen Nachteil: Es ist zur Compilezeit nicht >überschaubar, wie viel RAM man tatsächlich braucht. Und wenn die Daten >zu groß für den Speicher sind, gibt's einfach nur fehlerhaftes >Verhalten. Stimmt, aber nur so kommt man auf ein "Minimum" zur Laufzeit und kann nicht vergessen zu "putzen".
Sebastian S. schrieb: >>Es hat aber auch einen großen Nachteil: Es ist zur Compilezeit nicht >>überschaubar, wie viel RAM man tatsächlich braucht. Und wenn die Daten >>zu groß für den Speicher sind, gibt's einfach nur fehlerhaftes >>Verhalten. > Stimmt, aber nur so kommt man auf ein "Minimum" zur Laufzeit und kann > nicht vergessen zu "putzen". Vor allem, da der Compiler dann versuchen wird, so viel wie möglich ohne RAM-Nutzung in den Registern zu erledigen.
> Vor allem, ...
Compiler denken nicht mit. Es gibt auch keine Optimierung die
"so viel wie möglich ohne RAM-Nutzung in den Registern zu erledigen."
Das waere allenfalls ueber eine Optimierung in Richtung minimaler
Laufzeit des Programms steuerbar.
Im Stack gehaltene Variable sind am Funktionsende auch nicht
mehr zugreifbar.
Und ob ich die Variablen statisch deklariere oder in der main
vom Stack hole, macht keinen Unterschied.
Compiler schrieb: >> Vor allem, ... > > Compiler denken nicht mit. Es gibt auch keine Optimierung die > "so viel wie möglich ohne RAM-Nutzung in den Registern zu erledigen." Doch, natürlich gibt es die. Das dürfte sogar zu den ältesten Optimierungen überhaupt zählen. > Und ob ich die Variablen statisch deklariere oder in der main > vom Stack hole, macht keinen Unterschied. Es macht einen Unterschied. Darum ging es ja gerade. Bei statischen Variablen sehe ich bereits zur Linkzeit, wie viel Platz sie brauchen. Dafür brauchen sie den Platz über die gesamte Laufzeit des Programms. Automatische Variablen dagegen existieren nur während der Laufzeit der Funktion, in der sie definiert sind. Davor und danach existieren sie nicht. Dafür sieht man nicht, wieviel am Ende wirklich an Platz gebraucht wird. Allerdings werden sie wenn möglich rein in Registern gehalten, wodurch sie zum Teil gar keinen Speicher brauchen.
:
Bearbeitet durch User
vn nn schrieb: > Sebastian S. schrieb: >>>Es hat aber auch einen großen Nachteil: Es ist zur Compilezeit nicht >>>überschaubar, wie viel RAM man tatsächlich braucht. Und wenn die Daten >>>zu groß für den Speicher sind, gibt's einfach nur fehlerhaftes >>>Verhalten. >> Stimmt, aber nur so kommt man auf ein "Minimum" zur Laufzeit und kann >> nicht vergessen zu "putzen". > > Vor allem, da der Compiler dann versuchen wird, so viel wie möglich ohne > RAM-Nutzung in den Registern zu erledigen. Das macht der eh, wenn man nicht gerade Code hat, der nur mit -O0 richtig läuft. Was der AVR auch macht: Globale Variablen immer mit kompletter Adresse ansprechen (also 2-Wort-LDS-STS, statt via Frameptr+ofs). Wenn man viele Funktionen mit kleinem lokalem Speicherbedarf hat, dann kann die "Stack-Variante" günstiger sein als die "Globals-Variante". BTW, "static type var;" ist auch "global". Ich vermute die "static"-"Idee" stammt aus der 8051-Zeit. Dort sind Pointer-Zugriffe ein Horror. Wenn man dann vom AVR Richtung ARM schaut, der hat so viele breite Index-Register zur Verfügung, daß lokal praktisch immer besser ist als (viele) globale Variablen. Wenn schon "global/static", dann wenigstens keine einzelnen Variablen, sondern Strukturen aus zusammen verwendeten Variablen. Dann kennt der Compiler für jede Variable den Offset innerhalb der Struktur und braucht (innerhalb gewisser Grenzen, ARM:4k) nur eine Basisadresse. Andernfalls: Mem-Adresse relative zum PC aus Flash holen und auf genau eine Variable zugreifen. Für jede einzelne Variable ein anderes Adressregister, bis diese ausgehen und dann eventuell eben mehrfach Adressen laden. Mit bis zu 4K-struct: einmal Basisadresse laden und dann Zugriff über Offset. Selbst wenn man die 32bit nicht zum Rechnen braucht, die Adressierungsarten dieser CPU möchten man nicht mehr missen, hat man sie mal entdeckt.
Den Speicherverbrauch in den Funktionen kann man auch selbst heruasfinden. Wie viele Funktionen tief springt man, und welche Kombination hat am meisten Platz in lokalen Variablen deklariert. Rekursionen sollten vermieden werden.
:
Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.