Compile-Time-Polymorphismus;Runtime-Polymorphismus;Geschwindigkeit



  • Hi,

    ich habe mir folgendes kleines Programm geschrieben um den Geschwindigkeitsverlust bei polymorphen Funktionsaufrufen zu ermitteln.
    Dabei habe ich festgestellt das es rund 12% langsamer ist, meiner Meinung nach ein wenig zu viel, dafür das man immer zu lesen bekommt es sei nicht so teuer.
    Was mich weiterhin verwundert ist die Tatsache, dass im letzten Test, also wenn ich manuell die Funktionszeiger setze, es geringfügig schneller ist als wenn ich den Compiler seinen Job mit virtual deklarierten Funktionen tun lasse, aber eigentlich macht der Compiler doch auch nicht anderes daraus, demnach müsste es doch gleich schnell sein.

    Ich würde auch gerne noch Wissen an welchen Stellen man den Code optimieren könnte, kann man die polymorphen Aufrufe noch irgendwie schneller machen ?

    Im Programm leite ich mit 3 unterschiedlichen Methoden eine Klasse von einer Bsisklasse ab und rufe eine überschriebene Methode mehrere male in einer
    Funktion auf. Zwischen den Aufrufen ermittle ich die Geschwindigkeit.

    #include <iostream>
    #include <tchar.h>
    #include <windows.h>
    #include <cstdlib>
    #include <ctime>
    
    using namespace std;
    
    const int LOOP_COUNTER = 100000000;
    
    class Win32Timer
    {
    private:
    	LARGE_INTEGER mStartTime;
    	LARGE_INTEGER mFrequency;
    
    public:
    
    	void reset();
    	unsigned long getMilliseconds();
    };
    
    void Win32Timer::reset()
    {
    	QueryPerformanceFrequency(&mFrequency);
    	QueryPerformanceCounter(&mStartTime);
    }
    //-------------------------------------------------------------------------
    unsigned long Win32Timer::getMilliseconds()
    {
    	LARGE_INTEGER curTime;
    	LONGLONG newTicks;
    
    	QueryPerformanceCounter(&curTime);
    
    	newTicks = (curTime.QuadPart - mStartTime.QuadPart);
    	// Scale by 1000 in order to get millisecond precision
    	newTicks *= 1000;
    	newTicks /= mFrequency.QuadPart;
    
    	return (unsigned long)newTicks;
    }
    
    class RuntimePolymorph {
    public:
    	virtual void overload() {
    	  // do nothing
    	};
      virtual int  value() const {
        return 10;
      }
    };
    
    class RuntimeDerived : public RuntimePolymorph {
     int _dummy;
    public:
      void overload() {
        _dummy = rand()%1000;
      }
    
      int value() const {
        return _dummy;
      }
    };
    
    void runtime_fn(RuntimePolymorph *p)
    {
        for(int i=0;i<LOOP_COUNTER;++i)
        { 
          p->overload();
        }
    }
    
    template<class T>
    class CompileTimePolymorph {
    public:
      void overload() { // inline implizit angegeben 
        static_cast<T*>(this)->overload();
      }
      int value() const {
        return static_cast<const T*>(this)->value;
      }
    };
    
    class CompileTimeDerived : public CompileTimePolymorph<CompileTimeDerived> {
      int _dummy;
    public:
      void overload() {
        _dummy = rand()%1000;
      }
      int value() const {
        return _dummy;
      }
    };
    
    template<class T>
    void compiletime_fn(T *t)
    {
      for(int i=0;i<LOOP_COUNTER;++i)
      {
        t->overload();
      }
    }
    
    class ManualPolymorph {
    public:
      typedef void (*overloadFuncPtr )(ManualPolymorph*);
      typedef int  (*valueFuncPtr)(ManualPolymorph*);
    
      overloadFuncPtr overload_ptr;
      valueFuncPtr    value_ptr;
    };
    
    void overloadBase( ManualPolymorph *p )
    {
      // ...
    }
    
    int valueBase( ManualPolymorph *p )
    {
      return 10;
    }
    
    void construct_base(ManualPolymorph *ptr)
    {
      ptr->overload_ptr = &overloadBase;
      ptr->value_ptr    = &valueBase;
    }
    
    class ManualDerived {
    public:
      ManualPolymorph _base;
      int _dummy;
    };
    
    void overloadDerived( ManualPolymorph *p )
    {
      reinterpret_cast<ManualDerived*>(p)->_dummy = rand()%1000;
    }
    
    int valueDerived( ManualPolymorph *p )
    {
      return reinterpret_cast<ManualDerived*>(p)->_dummy;
    }
    
    void construct_derived(ManualDerived *ptr)
    {
      ptr->_base.overload_ptr = &overloadDerived;
      ptr->_base.value_ptr    = &valueDerived;
    }
    
    void manual_fn(ManualPolymorph *t)
    {
      for(int i=0;i<LOOP_COUNTER;++i)
      {
        (t->overload_ptr)(t);
      }
    }
    int _tmain(int argc, _TCHAR* argv[])
    {
    	//srand( (unsigned)time( NULL ) );
    
    	Win32Timer timer;
    
      srand( 0 );
    	timer.reset();
    
      CompileTimeDerived comp;
    
      compiletime_fn(&comp);
    
      unsigned long firstTest = timer.getMilliseconds();
      cout << "Dummy value : " << comp.value() << endl;
    	cout <<  firstTest<< endl;
    
      srand( 0 );
      timer.reset();
    
      RuntimeDerived run;
    
      runtime_fn(&run);
    
      unsigned long  secondTest = timer.getMilliseconds();
      cout << "Dummy value : " << run.value() << endl;
      cout << secondTest << endl;
    
      cout << "The 1.Test is " << ((float)(secondTest - firstTest)) / (float)secondTest * 100
           << " % faster." << endl;
    
      srand( 0 );
      timer.reset();
    
      ManualDerived man;
    
      construct_derived(&man);
    
      manual_fn(&man._base);
    
      unsigned long thirdTest = timer.getMilliseconds();
    
      cout << "Dummy value : " << man._base.value_ptr(&man._base) << endl;
      cout << thirdTest << endl;
    
    	return 0;
    }
    

    MfG Stefan



  • fällt mir nur mal so auf: mach doch mal ein basisbeispiel, das du dann mit time misst. ich weis nicht, ob man sich auf so interne mesung verlassen kann.
    edit: und mach ein referenzmodell ohne polimorphe aufrufe (also mit typeid rauskriegen, was für klasse, dann richtig casten und dann funktion aufrufen)



  • Die Timerklasse habe ich jetzt folgendermaßen modifiziert.

    class Win32Timer
    {
    private:
    	//LARGE_INTEGER mStartTime;
    	//LARGE_INTEGER mFrequency;
      clock_t zeroClock ;
    
    public:
    
    	void reset();
    	unsigned long getMilliseconds();
    };
    
    void Win32Timer::reset()
    {
    	//QueryPerformanceFrequency(&mFrequency);
    	//QueryPerformanceCounter(&mStartTime);
      zeroClock = clock();
    }
    //-------------------------------------------------------------------------
    unsigned long Win32Timer::getMilliseconds()
    {
    	//LARGE_INTEGER curTime;
    	//LONGLONG newTicks;
    
    	//QueryPerformanceCounter(&curTime);
    
    	//newTicks = (curTime.QuadPart - mStartTime.QuadPart);
    	//// Scale by 1000 in order to get millisecond precision
    	//newTicks *= 1000;
    	//newTicks /= mFrequency.QuadPart;
    
    	//return (unsigned long)newTicks;
    
      clock_t newClock = clock();
      return (unsigned long)((float)(newClock-zeroClock) / ((float)CLOCKS_PER_SEC/1000.0)) ;
    }
    

    Und die Funktion mit dem Laufzeit-Polymorphismus.

    void runtime_fn(RuntimePolymorph *p)
    {
      RuntimeDerived *derived = dynamic_cast<RuntimeDerived*>(p);
      if(p)
      {
        for(int i=0;i<LOOP_COUNTER;++i)
        { 
          derived->overload();
        }
      }
      else
        for(int i=0;i<LOOP_COUNTER;++i)
        { 
          p->overload();
        }
    }
    

    Oder wie meintest du das ?

    Hier mal ne Beispielausgabe wie das Programm bei mir läuft

    Dummy value : 174
    7911
    Dummy value : 174
    9073
    The 1.Test is 12.8072 % faster.
    Dummy value : 174
    9013
    Press any key to continue

    Undzwar mit der obigen Modifikation. Wie ich sehe nähert sich die 2. und 3. Variante in der Geschwindigkeit an, wobei die 3. aber immernoch schneller ist.

    Wäre interessant mal zu erfahren wie es so bei euch läuft.

    Mein Rechner ist ein Celeron 800Mhz mit 512 Mb Ram, falls ihr bessere Rechner habt, erhöht bitte die Schleifendurchläufe, weil man sonst nichts von dem Unterschied merkt.


Log in to reply