Performance: Lieber globale Variablen statt Zugriffsfunktionen?



  • 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.



  • Michael E. schrieb:

    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.

    Ja aber das geht ja nicht. Schau dir mal den Code im letzten Post von mir an. Statt dem Makro könnte dort auch "deine" inline Funktion stehen. Würde aber aus den selben Gründen wie beim Makro nicht gehen.



  • Hallo stefan-tiger,

    /* private Daten des Moduls A */
    static TMyStruct MyStruct;
    

    Na das static ist der Grund warum es nicht geht ...

    /* Daten des Moduls A */
    TMyStruct MyStruct;
    

    und

    extern TMyStruct MyStruct;
    

    in der h Datei, schon gehts ...

    Klar, so kann der 'User' dann doch direkt auf das struct zugreifen,
    was soo nicht gedacht war ... Muss in die Doku -> ModulA.h.

    Noch etwas:
    globale oder statische Variabeln sollten wann immer möglich vermieden werden.

    Besser soetwas ...

    /* ModulA.h */

    #ifndef MODULA_H
    #define MODULA_H

    typdef struct
    {
    int a;
    int b;
    ...
    } TMyStruct;

    // ohne function overhead
    #define MyStructSetA_Makro(myStruct,A) (myStruct)->a=(A);

    /* Prototyp /
    MyStructSetA_Funktion(TMyStruct
    myStruct, int a);

    #endif

    Das bietet zusätzlich die Möglichkeit mehrere, unterschiedliche Instanzen vom ModulA haben zu können und auch oder nur eine statische Instanz zu haben.

    Wie auch immer, inline bietet c nicht und c++ behält sich das Recht vor doch nicht zu inlinen (nutzt also auch nichts).
    Was bleibt sind defines, das funktioniert, aber auch mit Einschränkungen ...

    Gruß Frank



  • Frank Erdorf schrieb:

    ....

    Na das static ist der Grund warum es nicht geht ...

    /* Daten des Moduls A */
    TMyStruct MyStruct;
    

    und

    extern TMyStruct MyStruct;
    

    in der h Datei, schon gehts ...

    ....

    Genau das sage ich ja die ganze Zeit. Natürlich liegt es am static.
    Das mit dem Pointer auf Struct ist allgemein eine gute Idee, aber für die betrachtung des Scrops erstmal unwichtig.

    Das Problem ist aber, wenn MyStruct "echt" global ist, wie von dir vorgeschlagen, dann hat man keine Kontrolle mehr über seine eignen Daten.

    Die Set-Funktion könnte ja umfangreicher sein und irgendwelche Prüfungen durchführen. Das kann dann ein anderes Modul einfach umgehen.

    Aber wie ich schon eingangs erwähnt habe: Aus verschiedenen Günden (Performance, mehrere Instanzen) würden viele "echt" globale Variablen einsetzen. Man verzichtet bewusst auf den Schutz so einer static Variablen.


Anmelden zum Antworten