Performance: Lieber globale Variablen statt Zugriffsfunktionen?
-
Big Brother schrieb:
stefan-tiger schrieb:
Ein Modul soll seine speichernden Elemente selbst anlegen/mitbringen. Es gibt also niemand anderen der für einen etwas auf dem Stack anlegt.
Das kannst du so festlegen, ist aber keine Universalregel.
stefan-tiger schrieb:
Heap geht soweit ich weiss nur mit Speicherwewaltung. Das steht aber nicht zur Verfügung. Es muss/soll alles statisch angelegt werden. Und das wäre, würde ich sagen, das langsamste was man machen kann.
Wer sorgt für den Speicher für die statischen Variablen, wenn es keine Speicherverwaltung gibt?
Wie auch immer, es wäre schon hilfreich, wenn du mehr Infos rüberwachsen lässt, denn
Laufzeitvorteile von globalen Variablen gegenüber Get-/Setfunktionen sind meistens
vernachlässigbar.
Der Informationsgehalt der Aussage: "Die Größe eines Moduls soll von einem anderen Modul konsumiert werden" geht gegen null.Es soll so sein. Es arbeiten unterschiedliche Entwickler an der Software. Jeder an seinem Modul. Es gibt keinen "dritten" oder "zentrales Modul" der für einen die Variablen aufm Stack angelegn würde.
Für globale/statische Variablen braucht man keine dynamische Speicherverwaltung (im Sinne von malloc), der Compiler/Linker/usw. legt diese Variablen in bestimmte Sektionen. Oder hab ich was übersehen?
Zu meiner Aussage: Was versteht man daran nicht?
Fiktives Beispiel: Ein Solarpanel, dass per Servo der Sonne nachgeführt werden soll und daher von einem Mikrocontroller gesteuert wird.
Es gibt in der Software des Controllers z.B. ein Modul "Solarzelle" (Auslesen der Spannung per ADC) und ein Modul "Servo" (Ansteruerung des Schrittmotors in abhänigkeit der Spannung).
Jedes Modul (C-Datei) soll von einer anderen Person entwickelt werden.
Daher einigt man sich auf die Schnittstelle.Möglichkeit 1: Das Modul "Solarzelle" schreibt auf eine globale Variable "U = getADC(...)" und das Modul "Servo" kann diese auslesen "if(U >= 42)..".
Möglichkeit 2: Das Modul Solarzelle "versteckt" die Variable für die Spannung, macht also ein "static int U;" (nicht lokal!). Jetzt kann das Modul "Servo" nichtmehr direkt drauf Zugreiffen.
Dafür stellt Modul "Solarzelle" eine Funktion "GetU()" zur Verfühung.
Damit kann Modul "Servo" die Abfrage nun per "if(GetU() >= 42).." machen.Was ist nun besser, sprich, braucht weniger Maschinenbefehle und läuft schneller? Meiner Ansicht nach Möglichkeit 1.
Reelevant wird es nämlich wenn es sehr viele Module gibt und man durch eine "einfache" Design-Entscheidung im Vorfeld die Laufzeit signifikant optimieren kann.
-
Auf meiner Arbeit haben wir ähnliche Aufgabestellungen. Ich verstehe nicht so genau, was du genau mit "Modul" meinst, es wäre deshalb hilfreicher zu sagen, was ein Modul in eurer Software ist.
Wir haben z.b. für jeden Sensor ein Klasse: (Achtung, fiktive Namen)
typedef struct bib_canval { int nid; comm_layer *layer; ... } bib_canval; bib_canval *bib_canval_new(void); int bib_canval_set_nid(bib_canval *cv, int nid); int bib_canval_set_layer(bib_canval *cv, comm_layer *layer); ...
dann haben wir Klassen für Drehgeber, Motoren, AD-Wandler
typedef struct bib_rotary_encoder { int parm1; ... bib_canval *pos; } bib_rotary_encoder; bib_rotary_encoder *bib_rotary_encoder(void); int bib_rotary_encoder *bib_rotary_encoder_set_nid(bib_rotary_encoder * rot, int nid); ... typedef struct bib_motor { bib_canval *init; bib_canval *speed; bib_canval *xxx; ... }
Auf keinen Fall legen wir gloabe Variablen an, denn was passiert, wenn man von einer Klasse mehrern Instazen hat?
Unsere Steuerung macht dann
bib_rotary_encoder *enc[5]; for(i = 0; i < 5; ++i) { enc[i] = bib_rotary_encoder_new(); bib_rotary_enocder_set_nid(enc[i], i+1); bib_rotary_encoder_connect(enc[i]); } bib_motor *motor[10]; ....
wenn unsere Klassen (oder nenn sie Module) die Info per globale Variable speichern würden, könnte man kaum mehrere Instazen gleichzeitig haben.
-
supertux schrieb:
...
Ein Modul ist quasi eine C-Datei (manchmal auch mehrere) und ein paar Header-Dateien. Ein Modul kümmert sich um eine abgeschlossene Einheit/Aufgabe.
Es ist aber meist so, dass ein Modul auch gleich ein einizes Objekt ist. Mehrere Instanzen werden nicht benötigt. Daher keine Unterscheidung in Klassen und Instanzen.
Wie ich in deinem Beispiel wird dynamische Speicherverwaltung verwendet; hinter der new-Funktion steckt warscheinlich malloc oder Ähnliches.
Das soll aber bei mir nicht genutzt werden.Weiterhin sehe ich auch, dass es bei dem Beispiel ein übergeordnetes Modul "Steuerung" gibt, welches auch dafür zuständig ist die Objekte anzulegen.
Das ist ein anderes Konzept. Das ist aber nicht das was ich machen möchte.Da ein Modul auch selbständig laufende Bestandteile (Funktionen die regelmäßig von SCheduler aufgerufen werden) haben kann, die mit dem Speicher des Moduls arbeiten sollen, muss das Mosul seinen Speicher selbst anlegen/mitbringen. Es ist ein sehr dezentrales Konzept. Dein Beispiel ist hingegen ein sehr zentrales Konzept.
In deinem Beispiel wird schlieslich auch Get/Set-Funktionen verwendet.
Anmerkung: Mehrere Objekte könnte man aber auch mit globalen Variablen machen. Dann hat man eben mehr davon...
-
Prinzipiell und lehrbuchgemäß ist eine größtmögliche Kapselung erwünscht, also die static- Variante mit Get/Set- Funktionen. Ich erinnere mich an einen Abschnitt einer humoresken Anleitung, chaotischen Code zu produzieren, in der es hieß, man solle auch nur selten verwendete Werte global anlegen und das so begründen, daß die paar Mikrosekunden für Get/Set- Funktionen einfach die Performance zu stark senken würden.
Tatsächlich gibt es wenig Gründe, globale Variablen einzuführen, einer davon ist die Performance, aber auch nur, wenn man wirklich ein Performance- Problem hat, also statt ein paar Millionen Funktionsaufrufen ein paar Millionen Direktzugriffe bekommt oder ISRs beschleunigen muß.
Ein anderer, wenn man sehr, sehr viele Parameter hat, die sich gegenseitig beeinflussen und man keinen logischen Schnitt hinkriegt, was an Parametern in welches Modul besser paßt. Hab' mal an einem Projekt "brav" begonnen, das dann zum Set/Get- Gemurkse verkommen ist und daraufhin auf die Ini-Fileauswertung eine Globals- Verwaltung draufgesetzt - hat der Übersichtlichkeit gut getan. Bei der Überarbeitung haben sich aber auch die negativen Seiteneffekte von globals gezeigt, was die Sichtbarkeit und Überdeckung von Namen anbelangt.Wie auch immer, Du solltest höchst triftige Gründe haben, globals einzuführen und verschärft auf die Sichtbarkeit aufpassen.
-
Ich seh den Vorteil bei 1. in der Performance. Es muss kein call stattfinden mit dem ganzen Overhead, möglicherweise ist der Zugriff nur 1-2 Assemblerbefehle groß.
Ich schätze je nach Architektur ist man hier um Faktor 5-10 schneller. Was meint ihr?Nicht schaetzen sondern messen. Soviel Overhead erzeugt ein call nun auch nicht. Auch ein Faktor 5-10 ist utopisch und geht stark an der Realitaet vorbei. Wichtig ist die Performance in Bezug auf das gesamte Programm, Mirkooptimierungen fallen dabei kaum ins Gewicht.
-
Statt Funktionen verwende ich, wenn es WIRKLICH was bringt defines (MAKROS).
Die haben zwar viele Nachteile, vermeiden dann aber den Funktion Overhead.Gruß Frank
-
knivil schrieb:
Soviel Overhead erzeugt ein call nun auch nicht. Auch ein Faktor 5-10 ist utopisch und geht stark an der Realitaet vorbei.
Das sehe ich nicht so. Ein Funktionsaufruf ist im Vergleich zum einfachen Speicherzugriff extrem teuer.
Es müssen alle Registervariablen auf dem Stack gesichert, dann muß der aktuelle PC auf dem Stack gesichert und dann zum neuen Ziel verzweigt werden.
Beim Rücksprung muß der PC und dann die Register wiederhergestellt werden. Ein Performanceverlust um den Faktor 5-10 halte ich da für sehr untertrieben.knivil schrieb:
Wichtig ist die Performance in Bezug auf das gesamte Programm, Mirkooptimierungen fallen dabei kaum ins Gewicht.
Das ist allerdings wahr. Wir reden hier schließlich von wenigen Mikrosekunden. Wenn es wohl darum geht, daß die Solarpanels möglichst schnell der Sonne hinterherfahren, sollte es egal sein. Daß muß aber der Threadstarter selber entscheiden.
mfg Martin
-
mgaeckler schrieb:
knivil schrieb:
Soviel Overhead erzeugt ein call nun auch nicht. Auch ein Faktor 5-10 ist utopisch und geht stark an der Realitaet vorbei.
Das sehe ich nicht so. Ein Funktionsaufruf ist im Vergleich zum einfachen Speicherzugriff extrem teuer.
Es müssen alle Registervariablen auf dem Stack gesichert, dann muß der aktuelle PC auf dem Stack gesichert und dann zum neuen Ziel verzweigt werden.
Beim Rücksprung muß der PC und dann die Register wiederhergestellt werden. Ein Performanceverlust um den Faktor 5-10 halte ich da für sehr untertrieben.du redest hier doch eher von einem interrupt oder
-
interrupt| schrieb:
mgaeckler schrieb:
knivil schrieb:
Soviel Overhead erzeugt ein call nun auch nicht. Auch ein Faktor 5-10 ist utopisch und geht stark an der Realitaet vorbei.
Das sehe ich nicht so. Ein Funktionsaufruf ist im Vergleich zum einfachen Speicherzugriff extrem teuer.
Es müssen alle Registervariablen auf dem Stack gesichert, dann muß der aktuelle PC auf dem Stack gesichert und dann zum neuen Ziel verzweigt werden.
Beim Rücksprung muß der PC und dann die Register wiederhergestellt werden. Ein Performanceverlust um den Faktor 5-10 halte ich da für sehr untertrieben.du redest hier doch eher von einem interrupt oder
OK ich geb's zu: hab Mist erzählt. Wenn jede Funktion, die Registervariablen verwendet, diese selber sichert, muß das natürlich der Aufrufer nicht mehr machen. Ist einfach schon zu lange her.
mfg Martin
-
stefan-tiger schrieb:
Hallo, ich habe ein Beispiel geschreiben. Dort habe ich per objdump gesehen, dass die Get/Set-Zugriffsfunktionen leider nicht ge-inline-d werden, trotz -O2, obwohl diese nur z.B. eine einfache Zuweisungen enthalten.
Nur zur Sicherheit: Stehen die Funktionsdefinitionen denn auch im Header?
-
Michael E. schrieb:
stefan-tiger schrieb:
Hallo, ich habe ein Beispiel geschreiben. Dort habe ich per objdump gesehen, dass die Get/Set-Zugriffsfunktionen leider nicht ge-inline-d werden, trotz -O2, obwohl diese nur z.B. eine einfache Zuweisungen enthalten.
Nur zur Sicherheit: Stehen die Funktionsdefinitionen denn auch im Header?
Hallo,
nein die Definitionen stehen nicht in einem Header. Warum habe ich im selben Beitrag im nachfolgenden Satz erklärt. Nochmal anders formuliert:
Man kann keine Zugriffsfunktionen inline machen und über Header verteilen wenn diese auf Variablen zugreiffen sollen die nur Modul-Scope (globale static Variablen) haben.
Dazu müsst man das "static" weglassen. Dann hat man aber wieder "echte" globale Variablen und dann wäre die Zugriffsfunktion wieder überflüssig weil ein anderes Modul die Variable dann direkt lesen/schreeiben könnte.
-
mgaeckler schrieb:
interrupt| schrieb:
mgaeckler schrieb:
knivil schrieb:
Soviel Overhead erzeugt ein call nun auch nicht. Auch ein Faktor 5-10 ist utopisch und geht stark an der Realitaet vorbei.
Das sehe ich nicht so. Ein Funktionsaufruf ist im Vergleich zum einfachen Speicherzugriff extrem teuer.
Es müssen alle Registervariablen auf dem Stack gesichert, dann muß der aktuelle PC auf dem Stack gesichert und dann zum neuen Ziel verzweigt werden.
Beim Rücksprung muß der PC und dann die Register wiederhergestellt werden. Ein Performanceverlust um den Faktor 5-10 halte ich da für sehr untertrieben.du redest hier doch eher von einem interrupt oder
OK ich geb's zu: hab Mist erzählt. Wenn jede Funktion, die Registervariablen verwendet, diese selber sichert, muß das natürlich der Aufrufer nicht mehr machen. Ist einfach schon zu lange her.
mfg Martin
Wenn man in Assembler programmiert hat man hier noch Optionen. Aber bei C macht es der Compiler meist so, dass der ganze Kontext gesichert wird. Das hat nichts mit Interrupts zu tun. Dieses sichern kostet immer Laufzeit.
Zudem ist ein call oder jump oft nicht gut für die Pipeline.Ich sehe das nicht als Mikrooptimierung. Mikro ist es vll. für manche Leute deshalb weil wir aus vll. 5-10 Maschinenbefehlen (Get/Set-Calls) eben 1-2 Maschinenbefehle (Zugriff auf globale Variable) machen.
Wenn das aber an vielen Stellen in der Software und vor allem in Teilen die sehr oft pro Sekunde aufgerufen werden können die Auswirkungen dieser Optimierung sehr groß sein.
-
Frank Erdorf schrieb:
Statt Funktionen verwende ich, wenn es WIRKLICH was bringt defines (MAKROS).
Die haben zwar viele Nachteile, vermeiden dann aber den Funktion Overhead.Gruß Frank
Dafür braucht du aber auch "echt" globale Variablen.
Falls nicht würde mich interessieren wie dein Get-Makro mit Modul-globalen (static) Variablen funktioniert.
-
knivil schrieb:
Ich seh den Vorteil bei 1. in der Performance. Es muss kein call stattfinden mit dem ganzen Overhead, möglicherweise ist der Zugriff nur 1-2 Assemblerbefehle groß.
Ich schätze je nach Architektur ist man hier um Faktor 5-10 schneller. Was meint ihr?Nicht schaetzen sondern messen. Soviel Overhead erzeugt ein call nun auch nicht. Auch ein Faktor 5-10 ist utopisch und geht stark an der Realitaet vorbei. Wichtig ist die Performance in Bezug auf das gesamte Programm, Mirkooptimierungen fallen dabei kaum ins Gewicht.
Die Schätzung basiert auf analysierten Objekt-Dumps.
Natürlich bringen solche Optimierungen nur was wenn die Gesamtheit aller möglichen Stellen wo man vll. Get/Set-Funktionen aufruft Signifikant für die Gesamtlaufzeit sind.
Ob dieser Anteil signifikant ist oder nicht lässt sich nicht pauschal sagen.
Bei einer GUI-Applikation auf dem PC vll. nicht, bei einer Steuerung auf einem Mikrocontroller evtl. schon.
-
stefan-tiger schrieb:
Die Schätzung basiert auf analysierten Objekt-Dumps.
Blah blah.
-
pointercrash() schrieb:
Prinzipiell und lehrbuchgemäß ist eine größtmögliche Kapselung erwünscht, also die static- Variante mit Get/Set- Funktionen. ....
Tatsächlich gibt es wenig Gründe, globale Variablen einzuführen, einer davon ist die Performance, ...
Wie auch immer, Du solltest höchst triftige Gründe haben, globals einzuführen und verschärft auf die Sichtbarkeit aufpassen.Viele Lehrbücher wollen allgemeine Lösungen für allgemeine Dinge beibringen. Ich denke die meisten sind für PC-Software und geben eher den Ratschlag wartbare Software statt optimierter Software zu erstellen.
Aber was ist denn C? Ist es Ein- oder Ausgebe-Sprache, oder beides?
Wenn ich einen Text schreibe dann erstelle ich eine Quelle z.B. in LaTeX und eine Ausgabe als PDF.
Ich sehe C eher als Ausgabe-Sprache. Abstraktere Dinge wie Prüfung des Scopes, Klassen und Instanzen usw. sollte man nicht versuchen in C zu verwalten sondern es auf einer höheren Abstraktionsebene "modelieren".
Der C-Code und letztendlich das was nachher auf der CPU läuft sollte so optimal wie möglich sein. Wenn mich die Sprachmittel von C vor die Wahl stellen (a) gute Verwaltbarkeit / nicht optimale Performance oder (b) schlechte Verwaltbarkeit / optimale Persormance, dann sollte man sich für (b) entscheiden und die Verwaltung mit auf C "aufgesetzten" Tools (Code-Generatoren, Skripte, etc.) durchführen die das hoffentlich optimal machen.Natürlich existieren oft nicht solche Tools oder man verwendet Sie nicht und verwaltet seinen Code "zu Fuß" in der Sprache C. Dann hat man es natürlich auch besser wenn man sich ans Lehrbuch hält...
-
stefan-tiger schrieb:
pointercrash() schrieb:
Prinzipiell und lehrbuchgemäß ist eine größtmögliche Kapselung erwünscht, also die static- Variante mit Get/Set- Funktionen. ....
Tatsächlich gibt es wenig Gründe, globale Variablen einzuführen, einer davon ist die Performance, ...
Wie auch immer, Du solltest höchst triftige Gründe haben, globals einzuführen und verschärft auf die Sichtbarkeit aufpassen.Viele Lehrbücher wollen allgemeine Lösungen für allgemeine Dinge beibringen. Ich denke die meisten sind für PC-Software und geben eher den Ratschlag wartbare Software statt optimierter Software zu erstellen.
Aber was ist denn C? Ist es Ein- oder Ausgebe-Sprache, oder beides?
Wenn ich einen Text schreibe dann erstelle ich eine Quelle z.B. in LaTeX und eine Ausgabe als PDF.
Ich sehe C eher als Ausgabe-Sprache. Abstraktere Dinge wie Prüfung des Scopes, Klassen und Instanzen usw. sollte man nicht versuchen in C zu verwalten sondern es auf einer höheren Abstraktionsebene "modelieren".
Der C-Code und letztendlich das was nachher auf der CPU läuft sollte so optimal wie möglich sein. Wenn mich die Sprachmittel von C vor die Wahl stellen (a) gute Verwaltbarkeit / nicht optimale Performance oder (b) schlechte Verwaltbarkeit / optimale Persormance, dann sollte man sich für (b) entscheiden und die Verwaltung mit auf C "aufgesetzten" Tools (Code-Generatoren, Skripte, etc.) durchführen die das hoffentlich optimal machen.Natürlich existieren oft nicht solche Tools oder man verwendet Sie nicht und verwaltet seinen Code "zu Fuß" in der Sprache C. Dann hat man es natürlich auch besser wenn man sich ans Lehrbuch hält...
hm? versteh ich nicht, wieso willst du das auf einer höheren abstraktionsebene modellieren und warum sollte das schneller sein? überhaupt weshalb denkst du dass schöne software langsam ist
-
stefan-tiger schrieb:
nein die Definitionen stehen nicht in einem Header. Warum habe ich im selben Beitrag im nachfolgenden Satz erklärt.
Tschuldigung, hab ich übersehen. Das ist dann der Grund warum die Funktionsaufrufe nicht wegoptimiert werden: Die Definitionen stehen in ner anderen Übersetzungseinheit, sind also für den Compiler nicht sichtbar.
-
Hallo stefan-tiger
Thema Makros statt Funktionen
typdef struct { int a; int b; ... } TMyStruct; // mit function overhead MyStructSetA(TMyStruct* myStruct, int a) { myStruct->a = a; } // ohne function overhead #define MyStructSetA(myStruct,A) (myStruct)->a=(A);
Wo das Struct nun liegt, stack, heap, statisch ist erstmal egal.
So etwas habe ich schon ein paar mal gemacht.Normaler weise ist es, wie schon erwähnt,
sinnvoll den Code über Funktionen oder besser noch Scharen von Funktionen zu strukturieren.
Es gibt Stellen wo einem der Funktion Overhead zu teuer ist.
Man kann diese Stellen dann mit #defines quasi 'inlinien'
ohne Struktur zu verlieren.
Ist aber grausig zu debuggen, in jedem Fall mit Vorsicht zu genießen
und sicherlich nur selten nötig.Gruß Frank
-
/* ModulA.h */ #ifndef MODULA_H #define MODULA_H typdef struct { int a; int b; ... } TMyStruct; // ohne function overhead #define MyStructSetA_Makro(A) MyStruct.a=(A); /* Prototyp */ MyStructSetA_Funktion(int a); #endif
/* ModulA.c */ #include "ModulA.h" /* private Daten des Moduls A */ static TMyStruct MyStruct; // mit function overhead MyStructSetA_Funktion(int a) { MyStruct.a = a; }
/* ModulB.c */ #include "ModulA.h" void tolleFunktion(void) { /* Modul B will was in Modul A setzten */ MyStructSetA_Makro(42); /* geht nicht :-( */ MyStructSetA_Funktion(42); /* das geht ! */ } ...
Hallo, ich habe dein Code angepasst/ergänzt. Vll. wird jetzt klar was ich mein.