Muß ich für diese "Verschönerung" einen Preis bezahlen oder optimiert
der Compiler (GCC für ARM Cortex M3) die konstanten
Zeiger-Dereferenzierungen immer weg?
Viele Grüße
W.T.
Walter T. schrieb:> Muß ich für diese "Verschönerung" einen Preis bezahlen oder optimiert> der Compiler (GCC für ARM Cortex M3) die konstanten> Zeiger-Dereferenzierungen immer weg?
ich kenne zwar ARM nicht, aber auf x68 wird es sogar mit dem Referenz
teilweise besser, eine Verschlechterung konnte ich noch nie Feststellen.
Ich mache es auch aus gründe der Lesbarkeit oft so.
Da ein ARM immer über eine Adresse in einem Register auf Specher
zugreifen muß, kann der Compiler gar nicht anders, als
"Pointer-Variante" zu benutzen. Solange die Strukturgröße innerhalb
gewisser Grenzen (4k) bleibt, wird er die einzelnen Strukturelemente
über Offset relativ zur Adresse im gewählten Basis-Register ansprechen.
Die 4k gelten für M3. Ohne Thumb2 sind der Offset eher bescheiden (auf
AVR-Niveau 0..31).
Wichtig ist aber immer, daß es eine Struktur ist, nur (zufällig)
innerhalb einer 4k-Page gelinkte Daten bekommen vom Comiler jeweils ein
eigenes Adress-Regster zugeteilt. Er ahnt ja nicht, wo der Linker die
Daten hinlegt. Mit einer Struktur weiß er das genau. Dadurch sinkt der
Code-Umfang und die Register-Last deutlich.
(Das ist zumindest die Erkentniss, die ich durch Lesen des erzeugten
Codes gewinnen konnte ->.lss-File)
Walter T. schrieb:> gl_Textdata.w = calcotherstuff(gl_Textdata.x, gl_Textdata.y,> gl_Textdata.z, gl_Textdata.a, gl_Textdata.h);
Welchen Grund hat es, daß der Aufrufer die Struct erst umständlich
auseinandernehmen soll?
Das kann doch viel bequemer die aufgerufene Funktion tun. Und dann
brauchst Du nur ein Argument zu übergeben, nämlich die Adresse der
Struct.
Peter D. schrieb:> Welchen Grund hat es, daß der Aufrufer die Struct erst umständlich> auseinandernehmen soll?
weil die Funktion eventuell universell ist und keine Ahnung von der
äußeren Struct haben soll?
Walter T. schrieb:> Muß ich für diese "Verschönerung" einen Preis bezahlen oder optimiert> der Compiler (GCC für ARM Cortex M3) die konstanten
Eine Fingerübung für den Compiler. Der weiss hier ja, worauf der Pointer
zeigt. Bei Maschinen mit direkter Adressierung statischer Daten, aber
wenig Adressregistern, sollte er dann die direkte Adressierung
bevorzugen (AVR, 8bit-PIC, 8051), bei Maschinen ohne direkte
Adressierung (ARM, 32bit-PIC) die relative über Adressregister.
Idealerweise unabhängig davon, ob du das nun selber zu optimieren
versuchst oder nicht.
Peter II schrieb:> weil die Funktion eventuell universell ist und keine Ahnung von der> äußeren Struct haben soll?
Dann erklär mal, warum man dafür eine Struct nimmt und kein Array.
Peter D. schrieb:> Das kann doch viel bequemer die aufgerufene Funktion tun.
Hallo Peda,
die Funktionen gibt es gar nicht. Ich wollte das hiermit darstellen:
Walter T. schrieb:> // Die Funktionsnamen stehen stellvertretend für als Quelltext> ausgeführte Operationen
In Wirklichkeit handelt es sich um 10...20 Zeilen Quelltext, die die
Elemente des globalen Structs ordentlich durcheinanderwursten. Ich
wollte damit lediglich darstellen, warum ich es für eine gute Idee
halte, mit kurzen Bezeichnern innerhalb der Funktion hantieren zu
können.
Walter T. schrieb:> In Wirklichkeit handelt es sich um 10...20 Zeilen Quelltext, die die> Elemente des globalen Structs ordentlich durcheinanderwursten.
Dann würde ich dafür separate Funktionen schreiben (je eine für jede
Wurst). Und die bekommen dann die Struct übergeben.
Viele Compiler optimieren so, daß bis zu 3 Argumente in Registern
übergeben werden können. Weitere Argumente müssen erst umständlich im
Stack eingekellert werden.
Es kann daher durchaus sein, daß separate Funktionen weniger Code und
weniger Stackverbrauch bewirken. Schneller sind sie auf jeden Fall.
In der Regel nimmt man aber gerade deshalb eine Struct, weil jedes
Element eine bestimmte Bedeutung hat. Daher ist eine beliebige
Verwendung recht unwarscheinlich.
Peter D. schrieb:> Walter T. schrieb:>> In Wirklichkeit handelt es sich um 10...20 Zeilen Quelltext, die die>> Elemente des globalen Structs ordentlich durcheinanderwursten.>> Dann würde ich dafür separate Funktionen schreiben (je eine für jede> Wurst). Und die bekommen dann die Struct übergeben.> Viele Compiler optimieren so, daß bis zu 3 Argumente in Registern> übergeben werden können. Weitere Argumente müssen erst umständlich im> Stack eingekellert werden.> Es kann daher durchaus sein, daß separate Funktionen weniger Code und> weniger Stackverbrauch bewirken. Schneller sind sie auf jeden Fall.>> In der Regel nimmt man aber gerade deshalb eine Struct, weil jedes> Element eine bestimmte Bedeutung hat. Daher ist eine beliebige> Verwendung recht unwarscheinlich.
Walter versucht sich schon seit einiger Zeit dem Thema ARM/Cortex-Mn zu
nähern und schlägt dabei immer wieder lustige Haken.
Darf man nicht alles ernst nehmen ;-)
Peter D. schrieb:> Viele Compiler optimieren so, daß bis zu 3 Argumente in Registern> übergeben werden können.
Es sind vier (r0-r3), und das ist keine Optimierung sondern im ARM EABI
so fix definiert, damit die Ausgaben verschiedener Compiler kompatibel
sind. Bei nichstatischen Member-Funktionen in C++ ist der 0. Parameter
(in r0) der "this"-Pointer.
Walter T. schrieb:> Muß ich für diese "Verschönerung" einen Preis bezahlen oder optimiert> der Compiler (GCC für ARM Cortex M3) die konstanten> Zeiger-Dereferenzierungen immer weg?
Bei solchen Fragen hilft es, den Disassembler-Code anzusehen oder
einfach die Zeit zu messen. Das spart ewige Diskussionen wie hier...
Walter T. schrieb:> In Wirklichkeit handelt es sich um 10...20 Zeilen Quelltext, die die> Elemente des globalen Structs ordentlich durcheinanderwursten.
Das klingt etwas unsauber... So reduziert man effektiv die
Wiederverwendbarkeit des Codes - wenn du später auf die Idee kommst, von
dem ganzen 2 Instanzen anzulegen, musst du alle Funktionen anpassen...
Besser ist es, den Funktionen jeweils Pointer auf die Daten (structs) zu
übergeben, welche dann auf höchster Ebene (main(), ISR's) mit konkreten
Pointern gefüllt werden. Das ist dann eine simple Form von
Objektorientierung und Dependency Injection (und somit ggf. besser in
OOP-unterstützenden Sprachen wie C++ aufgehoben).
Mit etwas Glück ist das sogar effizienter, weil das wiederholte Laden
der Adresse der globalen Variable entfällt und einfach nur ein
Register-Wert erhalten bleibt.
Niklas G. schrieb:> Bei solchen Fragen hilft es, den Disassembler-Code anzusehen oder> einfach die Zeit zu messen. Das spart ewige Diskussionen wie hier...
Das Problem am Disassembler lesen ist: Wenn man im ARM-Assembler nicht
bibelfest ist, kann man bei Zeigeroperationen auch mal ganz schnell die
völlig falschen Schlüsse ziehen.
Ganz anders sieht es selbstverständlich bei arithmetischen Operationen
aus: Da ist es nicht weiter schwer, zu verstehen, was der Compiler
gebaut hat.
Niklas G. schrieb:> Das klingt etwas unsauber...
Klingt es. Ist es auch ein wenig. Die Funktion, bei der mir die Frage
nach dem Overhead gekommen ist, hat den Zweck, zweimal durch eine
Zeichensatztabelle durchzugehen, um Basislinienabstand, Ober- und
Unterlängen und die maximale Zeichenbreite und -Höhe zu bestimmen. Es
gibt keinen Grund, sie aufzuteilen. Es gibt auch keinen Grund, sie
großartig schnell zu bekommen, da sie nur ein einziges Mal beim
Programmstart durchläuft. Sie war lediglich der Anlaß, diese -meiner
Meinung nach absolut berechtigte- Frage zu stellen, ob bei der
Verwendung von struct-Zeigern Overhead entsteht.
Walter T. schrieb:> Die Funktion, bei der mir die Frage> nach dem Overhead gekommen ist, hat den Zweck, zweimal durch eine> Zeichensatztabelle durchzugehen, um Basislinienabstand, Ober- und> Unterlängen und die maximale Zeichenbreite und -Höhe zu bestimmen.
Und was hat das mit Deiner Frage zu tun, zur wahllosen Übergabe von
Strukturelementen?
Ein Zeichensatz ist typisch ein Array und kein Struct. Man kann das
Array noch zu einer Struct erweitern, die wichtige Parameter wie
Zeichenbreite, Höhe, Anzahl enthält. Nur ergibt es keinen Sinn, wenn ich
der selben Funktion mal die Breite und mal die Anzahl übergebe.
Peter D. schrieb:> Und was hat das mit Deiner Frage zu tun, zur wahllosen Übergabe von> Strukturelementen?
Nichts. Deswegen habe ich mir die Mühe gegeben, die Frage nach dem
Overhead von Zeigern auf globale Structs von der eigentlichen
Implementierung zu trennen.
Walter T. schrieb:> Peter D. schrieb:>> Und was hat das mit Deiner Frage zu tun, zur wahllosen Übergabe von>> Strukturelementen?>> Nichts. Deswegen habe ich mir die Mühe gegeben, die Frage nach dem> Overhead von Zeigern auf globale Structs von der eigentlichen> Implementierung zu trennen.
Nochmal:
Da ein ARM jeden Speicherzugriff über eine Adresse in einem Register,
also einem Zeiger, machen muß, führt deine explizite Ausformulieren
eines Zeigers zu identischem Code.
Was du irgendwann mal selber im Listfile nachzulesen lernen solltest.
Walter T. schrieb:> Ein kürzerer Name würde die Lesbarkeit innerhalb der Funktion deutlich> verbessern
Lenkt aber von der Tatsache ab, dass da auf eine globale Datenstruktur
zugegriffen wird und macht spätere Wartung der Code schwierig. Ich würde
entweder lassen wie es ist, oder die globale Variabele eine kürzere Name
verpassen, oder das ganze so re-factoren, dass die globale Variabele
verschwindet.
Zu deiner Frage: der Pointer wird ziemlich zuverlässig vom Compiler
weg-optimiert.
Peter D. schrieb:> Also eine Frage ohne jeglichen praktischen Nutzen.
Wenn Du dasen Wissen, welche Vor- und Nachteile bestimmte Konstrukte
haben, für nutzlos hälst: Ja.
Meine Frage wurde allerdings mittlerweile auch hinreichend geklärt.
Walter T. schrieb:> Wenn Du dasen Wissen, welche Vor- und Nachteile bestimmte Konstrukte> haben, für nutzlos hälst: Ja.
Dieses Konstrukt kommt bei mir in der Praxis nicht vor. Wenn ich >=2
Elemente einer Struct übergeben muß, dann übergebe ich den Pointer auf
die Struct und lasse die Funktion die nötigen Elemente zugreifen.
Das hat auch den Vorteil, daß diese Funktion besser lesbar wird, da die
Elementebezeichner angeben, welche Bedeutung sie haben.
Und ich kann später die Struct erweitern, umsortieren oder Typen ändern,
ohne das es Probleme gibt. Ich spare mir dadurch eine mögliche
Fehlerinstanz, die das separate Auspacken bedeutet.
Das Konstrukt, einer Funktion drei oder gar mehr Elemente desselben
Structs zu übergeben, kommt bei mir auch nicht vor. Genausowenig, wie
Funktionsnamen wie "foo()", "calcsomestuff()", "calcotherstuff()" oder
"hierpassiertetwas()" vorkommen. Es sind einfach nur Platzhalter dafür,
daß mit vielen Elementen des structs gearbeitet wird und deswegen ein
kurzer Name Vorteile hat.
Es ging mir die ganze Zeit nur um das Konstrukt, einer in einer Funktion
ein "lokales Alias" in Form eines Zeigers auf ein globales Struct
vorzuhalten, um die Übersichtlichkeit zu erhöhen.