[Performance] inline-Inspector vs. friend Direktzugriff



  • ich könnte mir vorstellen, dass er sowas meint

    class Kapsel {
    public:
      Kapsel();
      int* getX();
    private:
      int X;
      const int* pX;
    // Wo genau muss das Const hin, damit der Wert des Pointers
    // nicht verändert werden kann?
    };
    Kapsel::Kapsel() : X(0)
    {
      pX = &X;
    }
    // so
    int* Kapsel::getX() {
      return pX;
    }
    // oder so
    int* Kapsel::getX() {
      return &X;
    }
    

    Eigentlich könnte man das ja auch mit einer Referenz lösen.



  • @hehejo:
    Also, ich wuesste nicht, was deine Variante an Performance bringen sollte...

    Uebrigens:

    const int* pX; //Das Object, auf das gezeigt wird, kann nicht veraendert werden
    int* pX const; //Der Pointer kann nicht mehr veraendert werden.
    


  • re

    ich meinte das folgend:

    class klasse
    {
       private:
          int wert;
          ...
    
       public:
          int brauche_oft_wert_func(void);
          ...
    
    };
    
    int klasse::brauche_oft_wert_func(void)
    {
       // wenn die variable wert nicht geaendert wird reicht es auch so:
       int t_wert = wert;
       wert = machwas(t_wert) - machwasanderes(t_wert);
       char *buffer = new char[t_wert];
       for(int i = 0;i < t_wert;i++)
       {
          buffer[i] = t_wert + i;
       }
    }
    

    in der oberen memberfunktion wird wert oft gebraucht. ob man es nun als temporaere copy von wert, ne referenz auf wert oder nen pointer benutzt, haengt vom davon ab, was man damit macht. auf jeden fall erspart man sich so den weg ueber den this-pointer. muesste man mal ausprobieren, wie viel das im endeffekt is.

    Meep Meep



  • weils mich jetz doch mal genauer interessiert hab ich das mal ausgemessen. inwiefern das ein vernuenftiger test is, weiß ich nicht ;o)

    hab mal folgendes getestet und mit rdtsc gemessen:

    class klasse
    {
       private:
          int temp;
          int erg;
       public:
          void rechne1(void);
          void rechne2(void);
    };
    
    void klasse::rechne1(void)
    {
       temp = 100000;
       erg = 0;
       for(int i = 0;i < temp;i++
          erg += i;
    }
    
    void klasse::rechne2(void)
    {
       temp = 100000;
       int t_temp = temp;
       int t_erg = 0;
       for(int i = 0;i < t_temp;i++)
          t_erg += i;
       erg = t_erg;
    }
    

    rechne1 brauchte bei mir ca. 660000 ticks und rechne2 ca. 220000 ticks. also grad mal ein drittel.

    Meep Meep



  • Das ist ja alles schön und gut aber was hat das mit dem problem zu tun ?

    Leider hab ich keine ahnung wo intern der unterschied zwischen dem zugriff direkt auf den member durch nen freund und nem zugriff per inline-methode. Das wär aber wichtig zu wissen, um hier aussagen treffen zu können, vll weiss ja einer von den pros mehr.

    so far...



  • Meep Meep schrieb:

    weils mich jetz doch mal genauer interessiert hab ich das mal ausgemessen. inwiefern das ein vernuenftiger test is, weiß ich nicht ;o)
    hab mal folgendes getestet und mit rdtsc gemessen:

    ich hab deine test mal auch genommen.
    hab die anzahl um faktor 100 vergrößert, damit ich feine sekundenanzahl bei mir kriege.

    #include <iostream>
    using namespace std;
    
    typedef unsigned long long u64;
    
    inline u64 rdtsc(){
    	u64 r;
    	asm volatile(
    		"rdtsc"
    		:"=A" (r)
    	);
    	return r;
    }
    
    class klasse 
    { 
       private: 
          int temp; 
          int erg; 
       public: 
          void rechne1(void); 
          void rechne2(void); 
    }; 
    
    void klasse::rechne1(void) 
    { 
       temp = 10000000; 
       erg = 0; 
       for(int i = 0;i < temp;i++)
          erg += i; 
    } 
    
    void klasse::rechne2(void) 
    { 
       temp = 10000000; 
       int t_temp = temp; 
       int t_erg = 0; 
       for(int i = 0;i < t_temp;i++) 
          t_erg += i; 
       erg = t_erg; 
    } 
    
    int main(){
    	{
    		u64 start=rdtsc();
    		klasse k;
    		k.rechne1();
    		u64 stop=rdtsc();
    		cout<<(stop-start)/4.e7<<endl;
    	}
    	{
    		u64 start=rdtsc();
    		klasse k;
    		k.rechne2();
    		u64 stop=rdtsc();
    		cout<<(stop-start)/4.e7<<endl;
    	}
    }
    

    ausgabe
    2.80059
    1.97064

    naja, rechne2 ist deutlich schneller.

    aber andererseits ist deine optimierung so simpel, daß die sogar ein webdesigner machen könnte. mal schauen, ob der compiler das auch hinkriegt.

    ich schalte -O3 ein.

    ausgabe:
    1.67002
    0.584626

    krass!

    also wir lernen: ein webdesigner ist klüger als ein compiler.

    und wir lernen: der zugriff auf auto-variablen ist schneller als der zugriff auf attribute.

    und jetzt, wo das tolle messprogramm steht, mal messen, ob der direkte zugriff auf attribute schneller ist als der zugriff über inspektoren.

    void klasse::rechne1(void) 
    { 
       temp = 10000000; 
       erg = 0; 
       for(int i = 0;i < temp;i++)
          erg += i; 
    } 
    
    void klasse::rechne2(void) 
    { 
       temp = 10000000; 
       erg = 0; 
       for(int i = 0;i < getTemp();i++)
          erg += i; 
    }
    

    ausgabe:
    1.75442
    1.42221

    nee, ist nicht der fall. der inspektor ist schneller.

    wenn ich in der main() die messungen vertausche:
    1.90626
    1.45198

    aha, der ist schneller, den ich zuerst gemessen habe.

    das messprogramm müsste noch ein wenig verbessert werden. aber egal. inline-inspektoren oder dorektzugriff sind genausoschnell.

    wenn man die werte lokale cachen kann, wirds nochmal schneller.



  • Raptor schrieb:

    Uebrigens:

    const int* pX; //Das Object, auf das gezeigt wird, kann nicht veraendert werden
    int* pX const; //Der Pointer kann nicht mehr veraendert werden.
    

    Fast.

    const int* pX; //Das Object, auf das gezeigt wird, kann nicht veraendert werden
    int* const pX; //Der Pointer kann nicht mehr veraendert werden.
    

    Sieht schon besser aus.



  • Meep Meep schrieb:

    weils mich jetz doch mal genauer interessiert hab ich das mal ausgemessen. inwiefern das ein vernuenftiger test is, weiß ich nicht ;o)

    hab mal folgendes getestet und mit rdtsc gemessen:

    class klasse
    {
       private:
          int temp;
          int erg;
       public:
          void rechne1(void);
          void rechne2(void);
    };
    
    void klasse::rechne1(void)
    {
       temp = 100000;
       erg = 0;
       for(int i = 0;i < temp;i++
          erg += i;
    }
    
    void klasse::rechne2(void)
    {
       temp = 100000;
       int t_temp = temp;
       int t_erg = 0;
       for(int i = 0;i < t_temp;i++)
          t_erg += i;
       erg = t_erg;
    }
    

    rechne1 brauchte bei mir ca. 660000 ticks und rechne2 ca. 220000 ticks. also grad mal ein drittel.

    Meep Meep

    So wie das da steht verwundert mich das doch erheblich. Hätte nicht gedacht, dass die Compiler da noch einen Unterschied lassen.

    Dann hab ich das mal mit dem icc 8.1 getestet, und wenn man da die Optimierung einschaltet, erkennt der Compiler, dass in den Funktionen nichts getan wird und optimiert diese ganz weg.



  • womit der restliche sinn, meines eh schon sinnlfreien codes vollkommen entsorgt worden waere.

    Meep Meep



  • volkard schrieb:

    Meep Meep schrieb:

    weils mich jetz doch mal genauer interessiert hab ich das mal ausgemessen. inwiefern das ein vernuenftiger test is, weiß ich nicht ;o)
    hab mal folgendes getestet und mit rdtsc gemessen:

    ich hab deine test mal auch genommen.
    hab die anzahl um faktor 100 vergrößert, damit ich feine sekundenanzahl bei mir kriege.

    Ist es überhaupt sinnvoll so große Zeiträume mit rdtsc zu messen? Da läuft doch soviel anderes Zeug, dass das Ergebnis stark verfälscht sein muss, oder nicht?



  • klar wird es verfaelscht.
    aber ob ich jetz mit der systemzeit messe, oder mit rdtsc is ja egal. hab ja dann auch nur gerundete werte angegeben und nicht auf den tick genau. ich habs mit halt angewoeht mit rdtsc zu messen

    Meep Meep



  • Meep Meep schrieb:

    klar wird es verfaelscht.
    aber ob ich jetz mit der systemzeit messe, oder mit rdtsc is ja egal. hab ja dann auch nur gerundete werte angegeben und nicht auf den tick genau. ich habs mit halt angewoeht mit rdtsc zu messen

    Meep Meep

    Ich meinte auch die ein-zwei Sekunden von volkard und nicht deine Paar Takte.



  • jo hab falsch gelesen.

    womit und wie wuerdest du messen ?

    Meep Meep



  • Meep Meep schrieb:

    ich habs mit halt angewoeht mit rdtsc zu messen

    ganz falscher ansatz.
    du kannst mit rdtsc durchaus solche funktionen, die die chance haben, einmal durchzulaufen, ohne weggeschaltezt zu werden, also sagen wir mal mit laufzeiten im millisekundenbereich (sind ja noch 1000000 takte) auf 5 stellen genau ausmessen mit der einfachen schleife

    int left=1000;
    time mintime=maxvalue<time>();
    while(++left){
      time t=measure(funktion);
      if(t<mintime){
         mintime=t;
         left=1000;
      }
    }
    

    das mache ich immer beim wpc, wenn speed das kriterium ist. und es funktioniert wunderbar.



  • @volkard

    koenntest du mir da mal erklaren was du da genau machst ? vorallem, was is maxvalue und die whileschleife wuerd mich interessieren

    Meep Meep



  • Meep Meep schrieb:

    jo hab falsch gelesen.

    womit und wie wuerdest du messen ?

    Meep Meep

    Ich glaube im Bereich zwischen ganz kurz und lang gibt es keine gute Methode die CPU Zeit eines Prozesses zu messen. Für sehr kurze Bereiche kann man wohl rdtsc benutzen und für lange Bereiche times() oder getrusage(). Ich hab auch gelesen, dass der Kernel die CPU Zeit eines Prozesses nicht ganz korrekt mitführt.

    Wenn es denn funktionieren würde kann man auch clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts) nehmen, aber mir ist das noch nicht untergekommen.



  • Meep Meep schrieb:

    @volkard

    koenntest du mir da mal erklaren was du da genau machst ? vorallem, was is maxvalue und die whileschleife wuerd mich interessieren

    Meep Meep

    ganz einfach:
    der kern ist die erkenntnis, daß die zu messende funktion nicht *schneller* als die echt zum berechnen benötigten takte sein kann, aber durch dazwischenfunkende andere prozesse beliebig langamer. also misst man einfach 1000000 aufrufe und nimmt den kleinsten messwert. irgendwann mal wird es der zufall so ergeben, daß die optimal kurze laufzeit, also die, wo zufällig kein anderer prozess dazwischenfunkte, gemessen wird.

    also kern ist, daß ich oft messe und das minimum nehme.

    versuche zeigten, daß es saugeil ist, so lange zu messen, und immer das minimum sich zu merken, bis der messwert 1000-mal nacheinander nicht das minimum unterschreitet. ich liefere dazu keinen mathematischen beweis (der eh nur auf unsauberen annahmen über störende andere prozesse fußen könnte), sondern sage, daß ich damit sehr stabile messwerte erreiche. und glaibürdig werden die messwerte duch die stabilität.



  • Der inline-Instpector ist genau so schnell, wie der Direktzugriff ueber friend?? 😮
    Donnerwetter!! Wenn man bedenkt, das es friend unter anderem deswegen gibt, wenn Klassen eng miteinander zusammenarbeiten so die Performance zu steigern.
    Das haette ich jetzt nicht erwartet.

    Bin ganz begeistert von dem Feedback 👍



  • Also irgendwie finde ich die simple Optimierung von Meep Meep den Hammer!!
    Wie kann denn das sein, das der Compiler da nix machen kann?
    Die Performance Unterschiede sind ja nun nich von Pappe.



  • Raptor schrieb:

    Also irgendwie finde ich die simple Optimierung von Meep Meep den Hammer!!
    Wie kann denn das sein, das der Compiler da nix machen kann?
    Die Performance Unterschiede sind ja nun nich von Pappe.

    das hat was mit der geschichte von c++ zu tun.
    es ist eine entscheidung "by design" wie microsaft sagen würde.

    nehmen wir ein beispiel, die sort-funktion. sie ist üblicherweise als introsort gebaut, also einem mischmach aus quicksort und heapsort. vielleicht mit einem terminierer für kleine bereiche garniert und einem nachfolgenden insertionsort, um den wieder auszubügeln.

    ist das optimal? sicher nicht! ich lese vielleicht meistens dateien, die eine million adressen drin haben und sortiert sind und ändere pro sitzung nur 5 adressen. die einzusortierern mit introsort ist die hölle. schauen wir mal, ob quicksort oder selectionsort oder insertionsort (alle mot O(n^2) im zufälligren fall) nicht schneller sind. man findet eins mit O(n)! also O(5*n)==O(n). habe introsort für solche extremfälle nicht ausdiskutiert, aber selbst wenn es auch O(n) hätte, dann im faktiscchen vergleich sicher ne größenordnung mehr als direktes einsortieren mit insertionsort.

    weil an sich zu 1000 aufgaben bestimmt 50 verschiedene sortierfunktionen optimal sind, und weil man in c++ das optimum sucht, wird es dem compiler wirklich verboten, zu erkennen, was der user denkt.

    es wäre möglich, ein schleifenpaar, das ein quicksort ausführt, vom compiler automatisch erkennen zu lassen und durch introsort zu ersetzen. in 99.9% aller fälle wäre das sogar gut. aber nicht immer. extremprogrammierer wie ich würden sich voll angekackt fühlen, wenn sie den echt besten zur zeit bekannten algo für genau dieses problem gewählt hätten und der optimierer schmiert einen standard-algo drüber. in scriptsprachen darf er und saoll er das und das geht soweit bis java (ein lustiges zwischending).

    das hat zur folge, daß um genau zu sein der user auch seinen compiler kennen soll und wissen soll, ob cachen was bringt. der user iszt ja der, der weißm welche daten vorliegen werden., wird die schleife meistens nur einmal durchlaufen und ist cachen vergäudete zeit? oder wird sie meistens oft durchlaufen und ist cachen toll? das weiß der compiler in c++ gar nicht.

    in der lustigen sprache jave aber haben wir die rollen verdreht. da darf und kann der programmierer nicht so sachen entscheiden. da darf und kann der compiler entscheiden. er darf ordentliche datenfluss-analysen machen und in der tat bei einfachen problemen jeden c++-compiler wegputzen. während wir 10 jahre lang daran gerätselz haben, ob und wie man templates einseten kann, wie man in c++ echt außersprachliche dinge macht (statt eines besseren präprozessors), haben die in java sinnvolle sachen programmiert (also geld verdient) und ihren optimierer verbessert (ja, je einfacher die sprache, desto besser kann optimiert werden). wenn nicht bald was glaubwürdiges passiert, muss selbst ich wechseln. obwohl c++ genau die sprache ist, in der ich alles, was ich meine, einigermaßen elegant ausdrücken kann.


Anmelden zum Antworten