Forum: Mikrocontroller und Digitale Elektronik ROM-Auslastung von C++-Programm reduzieren


von Manuel W. (multisync)



Lesenswert?

Hallo zusammen,

zum Aneignen/Vertiefen von Programmierkenntnissen entwickle ich gerade 
privat an einem Projekt rum. Verwendeter Mikrocontroller: STM32F103C8T6. 
Ich habe einen funktionstüchtigen Versuchsträger der ausschließlich in C 
programmiert ist. Ich will nun das Projekt auf objektorientiertes C++ 
umstellen. Dafür habe ich nun einen Teil der Software nun als Klasse 
programmiert, und sie funktioniert auch wunderbar. Jedoch scheint sich 
die ROM-Auslastung drastisch zu erhöhen. Könnt ihr mir Tipps geben, wie 
ich ROM freischaufeln kann?

Anbei findet ihr:
 - robot-ausschlieszlich-c.map: MAP-File der C-only-Lösung
 - robot-mit-cplusplus-anteilen.map: MAP-File der erweiterten Lösung bei 
der das Modul "Trajectory" als C++-Klasse implementiert wurde.
 - stm32f103c8_flash.ld: Linkerskript.

Ich denke, dass das __etext-Symbol das Ende des benutzten ROMs markiert. 
Wenn das stimmt, sind bei der C++-Lösung 61k der 64k ROM belegt. Bei der 
C-only-Lösung sind nur runde 25k belegt.

Gibt es etwas das ich tun kann, um die ROM-Belegung zu reduzieren?

Folgende Switches habe ich gesetzt:
- isolate each function in a section
- remove unused sections
- don't catch exceptions
- no run-time-type-identification
- newlib nano-branch
- use C++ libraries
- no optimizations (Für die Entwicklungsphase würde ich das gerne 
beibehalten, da optimierter Code angeblich schwerer zu debuggen ist.)


Vielen Dank im Vorraus!
Manuel

von Dr. Sommer (Gast)


Lesenswert?

Manuel W. schrieb:
> - no optimizations (Für die Entwicklungsphase würde ich das gerne
> beibehalten, da optimierter Code angeblich schwerer zu debuggen ist.)

Das ist halt schon verkehrt. Optimierungen ausschalten und guten/kleinen 
Code erwarten macht halt keinen Sinn. Schalte mal -Os ein. Wenn du 
tatsächlich Probleme mit Debugging hast, verwende -Og oder doch -O0.
Ansonsten:
https://www.mikrocontroller.net/articles/ARM_GCC#Code-Gr.C3.B6.C3.9Fe_optimieren 
befolgen

Disassembly posten, dann kann man schauen was da viel frisst

von ягт ден троль раус (Gast)


Lesenswert?

Erst mal ueberlegen was C++ denn bringt.

Sie sollte die Dokumentaion wegen der Re-use verbessern, man bringt eine 
Struktur in die Dokumentation die vorher nicht da war.

Und sie sollte den Code dichter machen, wegen der Re-use von Code.

Wenn du also mehr Codespeicher benoetigst ist etwas noch nicht ganz 
optimal. Das bedeutet, dass zB Debugcode dabei ist, oder 
Ueberpruefungen.

Ich setze mal voraus, du verwendest keine dynamischen zur Laufzeit 
verlinkten Objekte, und keine dynamischen Objekte. Die beiden sollte man 
nicht verwenden, denn sie sind fehleranfaellig. Machen den Code viel 
unzuverlaessiger.

von Horst (Gast)


Lesenswert?

-ffunction-sections -fdata-sections mit --gc-sections mal ausprobiert?

von Manuel W. (multisync)



Lesenswert?

ягт ден троль раус schrieb:
> Ich setze mal voraus, du verwendest keine dynamischen zur Laufzeit
> verlinkten Objekte, und keine dynamischen Objekte. Die beiden sollte man
> nicht verwenden, denn sie sind fehleranfaellig. Machen den Code viel
> unzuverlaessiger.

Ich habe mein Objekt tatsächlich dynamisch zur Laufzeit instanziiert. 
Ich weiß, dass das den Code fehleranfälliger macht, aber ich wollte es 
trotzdem einmal ausprobieren, einfach um es mal gemacht zu haben. Ich 
habe dynamische Speicherallokation noch niemals benutzt.

Ich habe den Zeitpunkt der Instanziierung nun zur Kompilierzeit hin 
verschoben, und voilà, tatsächlich schrumpfte die Codegröße wieder 
beträchtlich! Sie betrug dann nur noch 29kB, 3kB mehr als mit reinem C. 
Das hätte ich mir nicht gedacht. Was ist der Grund dafür? Weil die 
Notwendigkeit für's Heap-Management entfällt?

Etwas übermütig geworden habe mich nach diesem Erfolgserlebnis gleich 
daran gemacht das nächste Modul auf .cpp umzustellen. Und zwar die 
Hauptdatei des Projekts, also jene in der main() definiert wird.

Obwohl da drinnen nichts objektorientiert ist und gar nichts dynamisch 
stattfindet (kein malloc() oder operator new), ging die Codegröße gleich 
wieder auf ~60kB hoch.


Anbei sind die zugehörigen Dateien:
 - robot-mit-cplusplus-anteilen-noheap.map: Codegröße 29kB
 - robot-mit-cplusplus-anteilen-noheap-trotzdem-grosz.map: Codegröße 
~60kB
 - robot-mit-cplusplus-anteilen-noheap-trotzdem-grosz.objdump: Codegröße 
~60kB

: Bearbeitet durch User
von Dr. Sommer (Gast)


Lesenswert?

Manuel W. schrieb:
> Was ist der Grund dafür? Weil die Notwendigkeit für's Heap-Management
> entfällt?

Die malloc Funktion und das zugehörige Zeug ist halt groß. Außerdem 
natürlich der Exception Handling Code wenn du new verwendest.

Deine Disassembly enthält jede Menge double Berechnungen. Vielleicht mal 
auf float oder Interesse umstellen. Außerdem Exception Handling Code. 
Der wird trotz -fno-exceptions eingebunden wenn du irgendwas tust, was 
Exceptions generieren kann. Überschreibe mal __cxa_pure_virtual

von Reginald L. (Firma: HEGRO GmbH) (reggie)


Lesenswert?

Ich kann in meiner IDE auf newlibnano umstellen, da sinkt der benötigte 
Speicher im Flash um 50% bei mir.

von Manuel W. (multisync)


Lesenswert?

Dr. Sommer schrieb:
> Manuel W. schrieb:
>> Was ist der Grund dafür? Weil die Notwendigkeit für's Heap-Management
>> entfällt?
>
> Die malloc Funktion und das zugehörige Zeug ist halt groß. Außerdem
> natürlich der Exception Handling Code wenn du new verwendest.

Hmm... Alles klar. Interessant, was da alles hineingeschwemmt wird.

> Deine Disassembly enthält jede Menge double Berechnungen. Vielleicht mal
> auf float oder Interesse umstellen. Außerdem Exception Handling Code.
> Der wird trotz -fno-exceptions eingebunden wenn du irgendwas tust, was
> Exceptions generieren kann. Überschreibe mal __cxa_pure_virtual

Das alles wundert mich ehrlichgesagt ziemlich.

 - type float verwende ich im Code durchaus, aber type double habe ich 
wirklich nirgendwo verwendet.
 - Den Einfluss meiner Änderung auf's Exception-Handling verstehe ich 
auch nicht ganz. Ich habe wirklich nur mein .c-File in .cpp umbenannt, 
den Targetcompiler geändert (CPP anstatt CC), ein paar Compilerswitches 
gesetzt (zB "don't catch exceptions") und eingebundenen C-Header über 
extern "C" geklammert. Ich instanziiere aber keine Objekte oder 
ähnliches. Da drin ist nur stinknormaler C-Code.

Das mit __cxa_pure_virtual probiere ich mal.

von Dieter W. (dds5)


Lesenswert?

Dr. Sommer schrieb:
> Vielleicht mal auf float oder Interesse umstellen.

LOL.
Der Korrektur-Automat lässt grüßen!

von Manuel W. (multisync)


Lesenswert?

Ich habe nun
 - __cxa_pure_virtual definiert
 - und folgenden Artikel gefunden: 
http://www.embedded.com/design/mcus-processors-and-socs/4007134/Building-Bare-Metal-ARM-Systems-with-GNU-Part-4
Grund dafür:

Die Codebeispiele hinter folgenden Absätzen habe ich hinzugefügt.

"Most low-end ARM-based MCUs cannot tolerate 50KB code overhead. To 
eliminate that code you need to define your own, non-throwing versions 
of global new and delete, which is done in the module mini_cpp.cpp 
located in the directory cpp_blinky1."

"Finally, if you don't use the heap, which you shouldn't in robust, 
deterministic applications, you can reduce the C++ overhead even 
further. The module no_heap.cpp provides dummy empty definitions of 
malloc() and free():"
1
#include <stdlib.h> // for prototypes of malloc() and free()
2
3
// ...............................
4
extern "C" void *malloc(size_t) {
5
  return (void*)0;
6
}
7
8
// ...............................
9
extern "C" void free(void *) {
10
}
11
12
// .................................................................
13
void *operator new(size_t size) throw() { return malloc(size); }
14
// .................................................................
15
void operator delete(void *p) throw() { free(p); }
16
// .................................................................
17
extern "C" int __aeabi_atexit(
18
        void *object,
19
        void (*destructor)(void *),
20
        void *dso_handle)
21
{
22
        return 0;
23
}
24
25
void __cxa_pure_virtual()
26
{
27
28
}

Leider ist die Codegröße nach wie vor bei ~60kB...

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Vielleicht hilft Dir dieser Link: 
https://www.gitbook.com/book/arobenko/bare_metal_cpp/details

Ansonsten, wenn möglich auf den heap verzichten, RTTI und exceptions 
ausschalten. Dann gibt es eigentlichen keinen Grund, warum Deine C++ 
Variante größer sein sollte.

Vielleicht fängst Du mal damit an, Dein C code mit einem C++ compiler 
übersetzbar zu machen. Dann ist offensichtlich, dass sich die 
Code-Größen nicht unterscheiden dürften.

mfg Torsten

von Manuel W. (multisync)


Lesenswert?

Ich habe jetzt das Problem gefunden!

Ich musste einfach nur Definitionen für die symbols __cxa_end_cleanup 
und __gxx_personality_v0 hinzufügen.
1
extern "C" void __cxa_end_cleanup(){};
2
extern "C" void __gxx_personality_v0(){};

Wie bin ich darauf gekommen?

Wenn man sich die bereits geposteten map files
 - robot-mit-cplusplus-anteilen-noheap.map
 - robot-mit-cplusplus-anteilen-noheap-trotzdem-grosz.map
näher ansieht, springt es einem eigentlich eh gleich in's Auge.

Ganz zu Beginn des Dokuments beschreibt der Linker nämlich, welche 
Objekte er einbindet, und welche Referenzen der Grund dafür sind:
1
c:/program files (x86)/emblocks/2.30/share/em_armgcc/bin/../lib/gcc/arm-none-eabi/4.7.3/../../../../arm-none-eabi/lib/armv7-m\libstdc++_n.a(eh_arm.o)
2
                              obj\release\src\main.o (__cxa_end_cleanup)

Ein Diff macht klar, dass bei der C++-Lösung deutlich mehr Objekte 
hineingelinkt werden. Object A referenziert ein Symbol aus Object B, 
welches ein Symbol aus Object C referenziert usw. usf. An der Wurzel 
dieser ganzen Abhängigkeiten stehen jedoch nur zwei Referenzierungen: 
Die von __cxa_end_cleanup und __gxx_personality_v0. Wenn ich also für 
diese selbst Definitionen bereitstelle, wird das Reinlinken der tausend 
anderen Objects auch verhindert.

Die Codegröße ist jetzt wieder auf dem Niveau der C-Lösung. :-)

Definitionen für operator new, operator delete, malloc() und delete() 
bereitzustellen war -- in meinem Fall zumindest -- nicht nötig. 
Vielleicht kommt das aber noch.

: Bearbeitet durch User
von Manuel W. (multisync)


Lesenswert?

Torsten R. schrieb:
> Vielleicht hilft Dir dieser Link:
> https://www.gitbook.com/book/arobenko/bare_metal_cpp/details

Danke für den Link, das Buch ist sehr interessant!

Torsten R. schrieb:
> Ansonsten, wenn möglich auf den heap verzichten, RTTI und exceptions
> ausschalten. Dann gibt es eigentlichen keinen Grund, warum Deine C++
> Variante größer sein sollte.
>
> Vielleicht fängst Du mal damit an, Dein C code mit einem C++ compiler
> übersetzbar zu machen. Dann ist offensichtlich, dass sich die
> Code-Größen nicht unterscheiden dürften.

Genau das mache ich doch gerade...

: Bearbeitet durch User
von ./. (Gast)


Lesenswert?

>  - type float verwende ich im Code durchaus, aber type double habe ich
> wirklich nirgendwo verwendet.

Da reicht schon ein:

3.1415926

wo eigentlich ein:

3.1415926F

hinmuesste.

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
Noch kein Account? Hier anmelden.