Laden...

Genaue Zeitfunktion

Letzter Beitrag vor 14 Jahren 16 Posts 19.669 Views
Genaue Zeitfunktion

Hallo,

zur Frameratenberechnung setze ich zur Zeit noch "Environment.TickCount" ein, was mir aber aufgrund der Millisekunden noch zu ungenau ist.

Welche Funktionen gibt es noch, die mir eine genauere Zeitangabe als ms liefern?

Hallo Rapthor

Wenn du das FW2.0 verwendest, sollte die Klasse Stopwatch genauer sein.

Dankeschön, das hat mir geholfen!

Hallo Rapthor

Am genausten geht sowas IMO mit der API Funktion "QueryPerformanceCounter" und "QueryPerformanceFrequency".

Anbei meine Klasse, die ich selber in einer DirectX Applikation benutze.
Benutzung sollte selbsterklärend sein.


using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;

namespace SharpKrush.Misc
{
    /// <summary>
    /// Gibt den FPS Wert, der in einem gewissen Intervall aktualisiert wird, zurück.
    /// </summary>
    public class FrameCounter
    {
        [DllImport("kernel32.dll")]
        private static extern bool QueryPerformanceCounter(out long lpPerformanceCount);

        [DllImport("kernel32.dll")]
        private static extern bool QueryPerformanceFrequency(out long frequency);

        private System.Windows.Forms.Timer _timer;
        private float _fps;    // Fps Wert der im Intervall mit dem actFps befüllt wird
        private float _avgFps;
        private float _minFps;
        private float _maxFps;

        private float _sum;
        private float _sumCount;

        private float _actFps; // aktueller Fps Wert (Nach dem Render Timing)
        private long _lastFrame;
        private long _freq;
        private long _currentFrame;
        private int _interval;

        /// <summary>
        /// Konstruktor
        /// </summary>
        /// <param name="interval">Intervall der Aktualisierung vom FPS Wert, in Millisekunden</param>
        public FrameCounter(int interval) {
            this._sum = 0f;
            this._sumCount = 0f;

            // Min- und MaxFps Werte default setzen
            this._minFps = 999; // Das eigentlich jeder Wert kleiner ist~
            this._maxFps = 0;

            this._timer = new System.Windows.Forms.Timer();
            this._timer.Start();
            this._timer.Tick += new EventHandler(this.OnTick);
            this._timer.Interval = interval;
        }

        /// <summary>
        /// Gibt den Fps Wert zurück
        /// </summary>
        public float Fps {
            get { return this._fps; }
        }

        /// <summary>
        /// Gibt den durchschnittlichen Fps Wert zurück
        /// </summary>
        public float AvgFps {
            get { return this._avgFps; }
        }

        /// <summary>
        /// Gibt den kleinsten Fps Wert zurück
        /// </summary>
        public float MinFps {
            get { return this._minFps; }
        }

        /// <summary>
        /// Gibt den grössenn Fps Wert zurück
        /// </summary>
        public float MaxFps {
            get { return this._maxFps; }
        }

        /// <summary>
        /// Intervall der Aktualisierung vom FPS Wert, in Millisekunden
        /// </summary>
        public int Interval {
            set { this._interval = value; }
        }

        /// <summary>
        /// Beginnt mit der Zeitmessung
        /// </summary>
        public void Begin() {
            QueryPerformanceFrequency(out this._freq);
            QueryPerformanceCounter(out this._lastFrame);
        }

        /// <summary>
        /// Berechnet am Ende des Renderns den aktuellen Fps Wert
        /// </summary>
        public void End() {
            QueryPerformanceCounter(out this._currentFrame);
            this._actFps = this._freq / (this._currentFrame - this._lastFrame);

            // Auf kleinsten Wert prüfen
            if (this._actFps < this._minFps) {
                this._minFps = this._actFps;
            }

            // Auf grössten Wert prüfen
            if (this._actFps > this._maxFps) {
                this._maxFps = this._actFps;
            }
        }

        /// <summary>
        /// Berechnet den durchschnittlichen Fps Wert
        /// </summary>
        private void CalculateAvgFps() {
            if (this._sumCount <= 600) {
                this._sum += this._fps;
                this._sumCount++;

                this._avgFps = this._sum / this._sumCount;
            } else {
                this._sum = 0;
                this._avgFps = 60;
            }
        }

        /// <summary>
        /// Nach jedem Intervall wird der FPS Wert hier aktualisiert
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnTick(object sender, EventArgs e) {
            this._fps = this._actFps;
            this.CalculateAvgFps();
        }
    }
}

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

Hallo,

obwohl ich weiß dass dieses Thema alt ist muss ich eine Ergänzung machen. Dies auch deshalb weil Peter Bucher in seinem aktuellsten Blog-Eintrag sich darauf bezieht.

die Stopwatch verwendet intern den QueryPerformanceCounter und somit dürfte keine Unterschied zwischen den beiden bestehen.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

Hallo gfoidl

Danke für die Info, das wusste ich noch nicht.

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

Hallo,

in einer Diskussion im Ogre-Forum (3D Grafikbibliothek) geht es auch gerade um FPS-Messungen (Bilder pro Sekunde) und FPS-Begrenzung (nicht schneller als xxx FPS rendern).
Als ich auf den QueryPerformanceCounter hinwies, bekam ich eine Antwort, daß das (heutzutage) Probleme verursachen kann:

Using the QueryPerformanceCounter functions can cause problems on multi-core CPUs, at least there were some serious problems in the past. I don't know if the .NET Stopwatch class is aware of the problem and contains a fix.

On most machines you should be fine using any of these methods. If you have strange effects, just remember that you might have to take care of these bugs.

A little more detail:
On multi-core CPUs the performance counter on different cores can have different values. If the scheduler changes your program flow to another core, you might get strange readings like large differences between calls. If you create a time delta every frame (currentTimeStamp - lastTimeStamp) and you get negative values, you just encountered this problem.

The easiest way is to use a background thread with an affinity mask that binds it to only one core. So the functions will always use the same performance counter and the problem is solved. You'll have to do this in C or C++ code, because .NET has its own internal scheduler that is independent of the system scheduler and you can't change the affinity mask in a reliable way.

More information about QueryPerformanceCounter problems can be found
>
and
>
.

Auch wenn die Zeitmessung ausschließlich auf einem CPU-Kern stattfindet, kann ich mir Probleme vorstellen, da sich bei modernen CPUs die Taktfrequenz dynamisch ändern kann. (Es sei denn, man liest ständig QueryPerformanceFrequency aus und beachtet Änderungen in den Zeitberechnungen. Trotzdem wären dann Zeitvergleiche komplizierter zu berechnen.)

Frage:
Gibt es vielleicht eine Bibliothek für präzise Zeitmessungen, die man bei .NET-Programmen einbinden kann und die oben genannten Probleme berücksichtigen? Ich hatte selber schon gesucht, aber nichts passendes gefunden.

Oh, jetzt habe ich doch noch etwas interessantes gefunden:

Hallo Xqgene

wenn ich mit Stopwatch 10 mal in einer Schleife die Zeit für eine leere Anweisung messe, bekommt ich auf meinem Rechner exemplarisch folgende Ausgabe in Ticks (Jeder Tick stellt laut Doku ein Intervall von 100 Nanosekunden dar):

6
5
5
4
5
5
5
5
4
5

Also Schwankungen im Bereich von 200 Nanosekunden. Ich glaube das disqualifiziert auch die ansich nützliche Stopwatch für Messungen im Bereich von 0.1 - 5 nanosec. 😃

herbivore

Mit der Stopwatch-Klasse kann man also Zeitintervalle fast mit Tick-Genauigkeit messen.
Ich hoffe, da gibt es nicht so die Probleme wie beim QueryPerformanceCounter.

Hallo Beauty,

siehe ein paar Antworten weiter oben 😉

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

Ah, Du meinst das:

die Stopwatch verwendet intern den QueryPerformanceCounter und somit dürfte keine Unterschied zwischen den beiden bestehen.

Theoretisch könnte es sein, daß die Stopwatch-Klasse schwankende CPU-Taktungen berücksichtigt. Vielleicht finde ich dazu was in der Doku.

Dennoch bleibt noch die Frage offen, ob es möglicherweise eine alternative (externe) Bibliothek für sowas gibt. Daher ist hochscrollen keine ultimative Lösung 😜

Hallo,

Theoretisch könnte es sein, daß die Stopwatch-Klasse schwankende CPU-Taktungen berücksichtigt

Könnte sein, ist aber nicht umgestetzt. Die Stopwatch ist nur ein "Wrapper" für QueryPerformanceCounter.

ob es möglicherweise eine alternative (externe) Bibliothek für sowas gibt.

Ich kenn keine - hab aber auch nicht danach gesucht 😉
Fürs Messen muss halt mit statistischen Mitteln gearbeitet werden - absolute Messungen sind auf einem Betriebssystem wie Windows sowieso nicht möglich (Multitasking, ...).
Für das von dir oben (hochscrollen ^^) angesprochene FPS-Begrenzung ist das allerdings auch keine befriedigende Lösung...(das soll ja zur Laufzeit direkt passieren oder?)

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

Mir geht es nicht um einzelne Mikrosekunden (oder gar Nanosekunden wie in einem anderen Thread), sondern nur darum, etwas genauer zu sein als wie die Rückgabe von DateTime.Now.Ticks.

Früher dachte ich blauäugig, die Zeitabfrage wäre wirklich so genau (etwa 100ns), aber später bemerkte ich eigenartige Effekte bei einem Programm. Ursache war, daß der Zeitwert von DateTime.Now.Ticks lange nicht so oft aktualisiert wird, wie der Name es vermuten läßt. Ich weiß die Aktualisierungsabstände nicht mehr. Vielleicht wurde etwa jede Millisekunde der Wert aktualisiert.

Der QueryPerformanceCounter ist eigentlich eine tolle Sache, aber die oben genannten (möglichen) Probleme machen mich etwas skeptisch.

Ob die CPU-Frequenz sich ändert, sollte für QueryPerformanceFrequency irrelevant sein, denn:

The frequency cannot change while the system is running.

Quelle: QueryPerformanceFrequency Function

Das Problem sind nicht die variablen CPU-Frequenzen sondern wie schon erwähnt v.a. die Anwesenheit mehrerer Kerne.

Hier noch einige Tipps zum Thema Timing: Game Timing and Multicore Processors

Grüße,
Andre

Sehr nützlicher Artikel - darüber habe ich auch weitere relevante Seiten gefunden.

Danke (-:

Hallo leute,

Oh, jetzt habe ich doch noch etwas interessantes gefunden:

(Jeder Tick stellt laut Doku ein Intervall von 100 Nanosekunden dar)

Das stimmt so nicht. Die 100ns beziehen sich auf die Ticks Property der DateTime Klasse, denn dort ist 1 Tick = 100ns.

Wenn du mit Ticks arbeiten willst kannst du folgendes schreiben:


Stopwatch sw = [...]
[...]
long ellapsedNanoSeconds = sw.EllapsedTicks / Stopwatch.Frequency;

Gruß Michael

Mein Blog
Meine WPF-Druckbibliothek: auf Wordpress, myCSharp

Hallo xxMUROxx,

da Stopwatch.Frequency die Frequenz des Zeitgebers als Anzahl der Ticks pro Sekunde angibt, bekommt man mit sw.EllapsedTicks / Stopwatch.Frequency die Anzahl der vergangenen Sekunden heraus. Wenn man die Anzahl der vergangenen Nanosekunden haben will, muss mal also noch mit 1000000000 (109) multiplizieren.

BTW: Da EllapsedTicks und Frequency longs sind, wird hier standardmäßig Integer-Arithmetik verwendet. Man muss also aufpassen, dass man weder einen Überlauf bekommt (der bei sw.EllapsedTicks * 1000000000 / Stopwatch.Frequency denkbar wäre) noch durch abgeschnittene Nachkommastellen im Zwischenergebnis das Ergebnis unbrauchbar ungenau macht (wie das bei sw.EllapsedTicks / Stopwatch.Frequency * 1000000000) der Fall wäre).

herbivore