Forum: Mikrocontroller und Digitale Elektronik Overhead struct-pointer


von Walter T. (nicolas)


Lesenswert?

Hallo zusammen,

ich habe Funktionen, die aufgrund der Tatsache, daß sie hauptsächlich 
globale structs bearbeiten, schlecht leserlich geworden sind:
1
Textdata_t gl_Textdata;
2
3
void foo(void)
4
{
5
    
6
    gl_Textdata.a = calcstuff(gl_Textdata.a, gl_Textdata.b);
7
    gl_Textdata.w = calcotherstuff(gl_Textdata.x, gl_Textdata.y, gl_Textdata.z, gl_Textdata.a, gl_Textdata.h);
8
    // Die Funktionsnamen stehen stellvertretend für als Quelltext ausgeführte Operationen
9
10
    [...]
11
}

Ein kürzerer Name würde die Lesbarkeit innerhalb der Funktion deutlich 
verbessern:
1
void foo(void)
2
{
3
    Textdata_t *const Txd = &gl_Textdata;
4
    
5
    Txd->a = calcstuff(Txd->a, Txd->b);
6
    Txd->w = calcotherstuff(Txd->x, Txd->y, Txd->z, Txd->a, Txd->h);
7
8
    [...]
9
}

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.

von Peter II (Gast)


Lesenswert?

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.

von Carl D. (jcw2)


Lesenswert?

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)

von Peter D. (peda)


Lesenswert?

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.

von Peter II (Gast)


Lesenswert?

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?

von (prx) A. K. (prx)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

von Peter II (Gast)


Lesenswert?

Peter D. schrieb:
> Dann erklär mal, warum man dafür eine Struct nimmt und kein Array.

weil eventuell unterschiedliche Datentypen verwendet werden?

von Walter T. (nicolas)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

von Carl D. (jcw2)


Lesenswert?

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 ;-)

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

von Walter T. (nicolas)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

von Walter T. (nicolas)


Lesenswert?

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.

von Carl D. (jcw2)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

Walter T. schrieb:
> Nichts.

Also eine Frage ohne jeglichen praktischen Nutzen.

von Eric B. (beric)


Lesenswert?

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.

von Walter T. (nicolas)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

von Walter T. (nicolas)


Lesenswert?

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.

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.