Programmieren mit C++

Visual C++

Systemnahe Lösungen mit VC++

Hochauflösende Zeitmessung

Wie kann man hochauflösende Zeitmessungen vornehmen?

Frage

Die nachfolgend vorgestellte Klasse CElapsed bietet eine Lösung des Problems auf Basis der Win32 API-Funktionen QueryPerformanceFrequency() und QueryPerformanceCounter(). Diese beiden Funktionen sind in WINBASE.H wie folgt deklariert:

BOOL QueryPerformanceCounter(
    LARGE_INTEGER *lpPerformanceCount);
BOOL QueryPerformanceFrequency(
    LARGE_INTEGER *lpFrequency);

Die Funktion QueryPerformanceCounter() liefert im Parameter lpPerformanceCounter die Anzahl der Ticks, die mit der durch QueryPerformanceFrequency() in lpFrequency gelieferten Frequenz multipliziert werden muß, um die tatsächlich vergangene Zeit zu ermitteln.

Der als Frequenz gelieferte Wert stimmt mit der Clock Frequenz der PII- und PPro-CPU überein, der Counter entspricht einem Zähler der CPU Clock Ticks. Dies führt zusammen zu einer extrem hohen Auflösung der Zeitmessung, bringt jedoch den Nachteil mit sich, daß zum einen die Funktionalität nicht von jeder CPU bereitgestellt wird, zum anderen besitzen verschiedene CPUs unterschiedliche Frequenzen, so daß die Frequenzwerte und die Ticks nicht direkt miteinander vergleichbar sind, sondern nur die realen Sekunden, die dem Produkt der beiden Werte entsprechen.

Die Klasse CElapsed ist somit letztlich ein Wrapper um diese beiden Funktionen und bietet ein wenig mehr Komfort als die beiden rudimentären API-Funktionen.

Der Datentyp LARGE_INTEGER, wird intern als __int64 abgebildet und nimmt die Frequenz wie auch die Startzeit auf.

class CElapsed
{
  private :
    __int64 Frequency;
    __int64 BeginTime;
    int Initialized;

Das Ermitteln des Frequenzwerts erfolgt im Konstruktor, der auch gleich im Member Initialized festhält, ob eine Frequenz ermittelt wurde und die Methoden der Klasse eingesetzt werden dürfen.

  public :
    CElapsed()
    {
      Initialized = QueryPerformanceFrequency(
                                   (LARGE_INTEGER *) &Frequency);
    }

Der Start der Zeitmessung erfolgt über die Methode Begin(), die zunächst die Zulässigkeit des Aufrufs überprüft und dann den aktuellen Stand des Counters.

    BOOL Begin()  
    {
      if (!Initialized)
        return 0;
 
      return QueryPerformanceCounter((LARGE_INTEGER *)&BeginTime);
    }

Analog dazu ermittelt die Methode End() den Stand des Counters und returniert die Differenz zwischen End- und Startzeitpunkt als Sekundenwert, der sich aus der Division der verstrichenen Ticks in elapsed durch Frequency ergibt:

      double End()
      {
        if( ! Initialized )
          return 0.0;
 
        __int64 endtime;
        QueryPerformanceCounter((LARGE_INTEGER *)&endtime );
 
        __int64 elapsed = endtime - BeginTime;
 
        return (double)elapsed / (double)Frequency;
      }

Die Methode Available() gibt Auskunft darüber, ob die Zeitmessung verfügbar ist (TRUE) oder nicht (FALSE), wobei letzteres nur dann möglich ist, wenn der Aufruf von QueryPerformanceFrequency() fehlgeschlagen ist.

Lösung

    BOOL Available() 
    { 
      return Initialized; 
    }

Als reine Hilfsfunktion liefert die Methode GetFreq() den Wert der Frequenz als LARGE_INTEGER.

Methode Available

    __int64 GetFreq()
    { 
      return Frequency; 
    }
};

Für den Einsatz der Klasse CElapsed sind nur wenige Aufrufe notwendig. Zunächst ist eine Instanz der Klasse zu erzeugen. Anschließend beginnt die Zeitmessung durch Begin() und wird durch End() beendet. Dazwischen können beliebige Aktionen ausgeführt werden.

Methode GetFreq

CElapsed Timer;
 
Timer.Begin();
 
// zu messende Aktion(en)
 
double elapsed = Timer.End();

Einschränkungen und Anmerkungen

Für den Einsatz der Klasse ist als Haupteinschränkung zu beachten, daß die Funktionen nicht auf jeder CPU zur Verfügung stehen. Aus diesem Grund muß die Verfügbarkeit der Funktionen stets korrekt überprüft werden. Bei allen Intel-CPUs der Pentium-Familie sollte dies jedoch gewährleistet sein.

Die Frequenz des Timers variiert in starkem Maße, da es sich letztlich um die CPU Clock handelt, wobei aber auch diese teilweise stark vom erwarteten Wert abweicht. Auf einem 133 MHz Pentium kommen schon mal 1.193 MHz heraus, während ein 200 MHz Pentium Pro mit 199.46 MHz läuft.

Die Funktion GetFreq() liefert die Frequenz als LARGE_INTEGER. Bedingt durch die Kalkulation

Zyklen / (Zyklen pro Sekunden)

werden aber immer korrekte Sekunden geliefert, egal, wie die Frequenz lautet.

Der Overhead der Methoden, die stets das Flag Initialized abfragen, dürfte in der Regel keine Rolle spielen, da dies nur bei sehr kurzen Zeitabständen zum Tragen kommt.

Beispiel





Sachgebiet


© 2009-2012 by Alojado Publishing. Alle Rechte vorbehalten. Ausgewiesene Marken gehören ihren jeweiligen Eigentümern.
Mit der Benutzung dieser Seite erkennen Sie die Nutzungsbedingungen und die Datenschutzerklärung an. Der Betreiber übernimmt keine Haftung für den Inhalt verlinkter externer Internetseiten.
Seite erzeugt 2012-05-20 02:20:02 von textarchiv.alojado.de