ARM Exception Handling GCC 4 va GCC 8
-
Hallo zusammen,
ich arbeite an einem ARM embedded projekt mit C++11.
Da man bei embedded Projekte ja immer mit dem Speicher etwas vorsichtig sein muss wollte ich mir mal den Speicherbedarf einer Exception anschauen.
Anbei findet ihr einen kleinen BeispielcodeWenn man den gleichen Code nun mit ARM gcc 4.5.4 (linux) und ARM gcc 8.2 kompiliert sieht man das der Assembler Output von 150 Zeilen (gcc 4.5.4) auf 77 Zeilen schrumpft (gcc 8.2).
Kann mir jemand sagen was sich hier geändert hat? Meine Assembler Kenntnisse sind etwas beschränkt. Meinem Kenntnissstand läuft das Exception Handling folgendermaßen ab:
-
Allokieren des Exeption Objekts auf dem Heap oder in einem statischen Speicher (__cxa_allocate_exception)
-
Zurückdrehen des Stack Pointers bis wir auf das erste try/catch stoßen
-
Nach Abhandlung des try/catch. Freigeben des Exception Objekts
An dieser Grundlegenden Sache hat sich doch eigentlich nie was geändert? Wie können die Assembler Zeilen dann so arg weniger werden?
-
-
Mit -O3 sind es beim 8.2 74, beim 4.5 86 Zeilen.
-
Die Zeilen 29 - 76 beim "ARM gcc 4.5.4 (linux)"-Output stellen die Implementierung der beiden Kopierkonstruktoren für
runtime_error::runtime_error(std::runtime_error const&)
undstd::exception::exception(std::exception const&)
dar, welche beim anderen Build nicht explizit erzeugt werden (obwohl beide diese in Zeile 124, bzw. beim gcc 8.2 in Zeile 59, benutzen) - evtl. wird dieser dann beim gcc 8.2 dazugelinkt.
-
Besser: mit
-O2
bauen und dielib.f
Option anhakerln (entfernt Code für Funktionen von externen Libraries). Da ist zwar auch GCC 8 besser (weniger Code), aber der Unterschied ist nicht gross. Und dürfte einfach daran liegen dass GCC 8 generell besser optimiert. Wäre ja auch traurig wenn sich zwischen 4 und 8 nix getan hätte.
-
alles klar vielen Dank für euere schnellen Antworten.
Ich versuche gerade zu prüfen ob es sich lohnt exceptions zu werfen oder nicht. (Wir verwenden zwar bereits eine dynamische allokierung, aber mir ging es in einem ersten Schritt um den zusätzlich generieren Code pro Exception, also der Teil vom Programm, der im Flash landet)
-
mir ging es in einem ersten Schritt um den zusätzlich generieren Code pro Exception, also der Teil vom Programm, der im Flash landet
Tipp: verwende mit "noinline" markierte Hilfsfunktionen um die Exceptions zu werfen.
AlsoBOOST_NOINLINE void throwInvalidArgumentException(char const* detail) { throw InvalidArgumentException(detail); } void foo(int x, int y) { if (x < y) throwInvalidArgumentException("x < y"); }
-
hey,
was ist denn so schlecht am Inlinen von 1 Zeilern?BOOST_NOINLINE void throwInvalidArgumentException(char const* detail) { throw InvalidArgumentException(detail); }
Ich dachte es wäre immer gut 1 Zeiler zu inleinen. (Die eine Zeile zusätzlich macht doch auch keinen Unterschied?)
Oder verhindere ich so, dass, dass ganze Exception Handling inlined wird?
-
@J-heni sagte in ARM Exception Handling GCC 4 va GCC 8:
Ich dachte es wäre immer gut 1 Zeiler zu inleinen.
Aha. Und wie machst Du das?
-
@J-heni
Wie fast immer beim Programmieren (und im Leben): es kommt drauf anZ.B. darauf wie der Konstruktor der Exception aussieht. Im besten Fall tut die eine Zeile nicht sehr weh. Aber angenommen die Exception nimmt einen
std::string
Parameter...
Nun, std::string ist die Instanzierung eins Templates (std::basic_string<char>
), und normalerweise auch "inline" Implementiert. Und üblicherweise auch ohne "Inline-Blocker". Dann kann es sein dass der Compiler meint es wäre furchtbar schlau auch noch den Konstruktor vonstd::string
zu inlinen. Der muss dann erstmal noch die Länge des Strings bestimmen weil ja derconst char*
Konstruktor verwendet wird. Nehmen wir weiters an die Funktion mit der die Länge bestimmt wird (z.B.strlen
) liegt auch inline vor, dann kann auch diese noch inlined werden. Im Endeffekt können da schnell ein paar hundert Byte Code draus werden.Mit einer "noinline" Hilfsfunktion zum Werfen der Exception verhinderst du diesen Wildwuchs.
Aber selbst wenn die Exception trivial zu konstruieren ist, ist die Befehlssequenz die für throw generiert wird länger. Sieh selbst:
Das Werfen der Exception ist hier
mov edi, 8 call __cxa_allocate_exception xor edx, edx mov esi, OFFSET FLAT:typeinfo for SimpleException mov QWORD PTR [rax], OFFSET FLAT:.LC0 mov rdi, rax call __cxa_throw
Und das Aufrufen der Hilfsfunktion ist
mov edi, OFFSET FLAT:.LC2 call throwSimpleException(char const*)
Und BTW das Werfen eines
std::runtime_error
(nicht in dem verlinkten Beispiel) ist noch mehr Code, vermutlich da dieser einen Desturktor hat und daher noch zerstört werden muss:mov edi, 16 call __cxa_allocate_exception mov esi, OFFSET FLAT:.LC4 mov rdi, rax mov rbp, rax call std::runtime_error::runtime_error(char const*) mov edx, OFFSET FLAT:_ZNSt13runtime_errorD1Ev mov esi, OFFSET FLAT:_ZTISt13runtime_error mov rdi, rbp call __cxa_throw mov r12, rax mov rdi, rbp call __cxa_free_exception mov rdi, r12 call _Unwind_Resume
-
ps
@J-heni sagte in ARM Exception Handling GCC 4 va GCC 8:
Oder verhindere ich so, dass, dass ganze Exception Handling inlined wird?
Exception Handling passiert dort wo die Exception gefangen wird, nicht wo sie geworfen wird. Da kann man auch Hilfsfunktionen einsetzen, aber das ist wieder ein anderes Thema.
Üblicherweise hat man aber viel mehr Stellen wo man Exceptions wirft als wo man welche fängt, daher ist das meist nicht so wichtig.
-
d.h. es wird nur dort "Zusatzcode" generiert wo die Exceptions aufgefangen werden?