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:> 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".
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
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.
>> 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?
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.
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.
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.