Laden...

[gelöst] Seltsamer Fehler mit Do-Schleife bei Zeitberechnung

Erstellt von Easyrider vor 15 Jahren Letzter Beitrag vor 15 Jahren 2.027 Views
E
Easyrider Themenstarter:in
200 Beiträge seit 2006
vor 15 Jahren
[gelöst] Seltsamer Fehler mit Do-Schleife bei Zeitberechnung

Servus,

habe ein etwas schräges Problem, bei dem ich nicht weiterkomme. Folgender, einfacher Code:


        private void Form1_Load(object sender, EventArgs e)
        {
            DateTime start, end;
            int loopMax = 3750;
            int counter;
            int diff;

            for (int i = 0; i < loopMax; i++)
            {

                start = DateTime.Now;
                counter = 0;

                do
                {
                    end = DateTime.Now;
                    counter++;
                    diff = end.Millisecond - start.Millisecond;

                    if (diff >= 40)
                    {
                        MessageBox.Show("Time: " + diff.ToString() + "\nMilliseconds to wait: 40\nCounter: " + counter.ToString());
                        break;
                    }
                }
                while (true);

            }
        }

Es geht in diesem Stück darum, das einfachste Grundgerüst für eine grafische Engine zu erstellen. Das hier ist der Teil mit der Framebremse auf 25 FPS. Mein Problem ist, das dieser Code nicht das macht, was er laut meiner Logik machen sollte.

Meine Denkweise:

  • Startzeit setzen
  • Beginne mit der Framebremse
  • Setze Endzeit, erhöhe Counter
  • Berechne die Millisekunden zwischen start und endzeit
  • Ist die Differenz größer gleich 40 ms (1 / 25 Minute), dann brich die Framebremse ab.
  • Fertig

Eigentlich ein extrem einfaches Konstrukt. Ich habe hier zu Demonstrationszwecken alle Dinge entfernt, welche nicht zwingend benötigt werden.

Mein Problem besteht darin, das er NICHT nach 40 ms aufhört wie geplant, sondern das er 46-47 ms braucht, bis die Bedingung erfüllt ist. Und das bei ca. 50.000-65000 Schleifendurchgängen. Hat jemand von euch schonmal dieses Phänomen erlebt? Oder kann mir jemand erklären, was ich falsch mache? Bin langsam etwas verzweifelt X(.

Im Anhang findet ihr eine Solution mit dem Beispielquellcode sowie das Bild von mir. Das Passwort dafür lautet "testpasswort" ohne Anführungsstriche.

mfg

Easy

630 Beiträge seit 2007
vor 15 Jahren

Hallo Easyrider,

du wirst Probleme haben dass so zu lösen da .NET-Timer relativ ugnenau sind (c.a 30 ms). Ausserdem kann der Garbage Collector einspringen oder ein Taskswitch im Betriebssystem stattfinden was die Codeausführung verzögert.

Fazit: So genaue Timings sind unter Windows ohne spezielle Hardware nicht möglich.

Gruss
tscherno

To understand recursion you must first understand recursion

http://www.ilja-neumann.com
C# Gruppe bei last.fm

49.485 Beiträge seit 2005
vor 15 Jahren

Hallo Easyrider,

Bild entfernt, wegen [Hinweis] Wie poste ich richtig? Punkt 6.1.

herbivore

E
Easyrider Themenstarter:in
200 Beiträge seit 2006
vor 15 Jahren

Hallo,

@ herbivore: Macht nix, habs ja extra in das RAR-File mit eingepackt. Beim nächsten Mal weiß ich es 😉

@ tscherno: Wenn die .NET Timer so ungenau sind, wie wird dann das in der Spieleprogrammierung gelöst? Ist DirectX etwa genauer? Oder gibt es andere Lösungsmöglichkeiten in dieser Hinsicht.

Habe bis jetzt schon viel Arbeit in die Möglichkeit gesteckt, in der Oberfläche zu zeichnen. Es wäre sehr schade, wenn mein Projekt nun an dieser Stelle scheitern würde. Weit über 100 Stunden arbeit umsonst ⚠

Für etwaige Lösungsmöglichkeiten würde ich mich sehr freuen!

630 Beiträge seit 2007
vor 15 Jahren

Hallo Easyrider,

keine Ahnung, kenne mich in der Spieleprogrammierung nicht so aus, würde aber annehmen das DirectX das mit der Framerate alleine regelt oder es dafür evtl. eine Funktion gibt.

Gruss
tscherno

To understand recursion you must first understand recursion

http://www.ilja-neumann.com
C# Gruppe bei last.fm

E
43 Beiträge seit 2007
vor 15 Jahren

Hi Easyrider,

Benutz doch einfach die Stopwatch Klasse aus dem System.Diagnostics Namespace. Die ist wesentlich genauer als Timer/DateTime.Now.
Mit der arbeite ich immer wenns um genaue Zeitmessung geht.

Viele Grüße
Daniel

"A train station is where a train stops. A bus station is where a bus stops. On my desk I have a workstation..."

JAck30lena:
"je nach format schwankt die komplexität zwischen 'kapier ich irgendwie nicht' und 'what the fuck...'"

630 Beiträge seit 2007
vor 15 Jahren

Hi ed-wards,

es stimmt, die Stopwatch ist genauer, aber auf die Genauigkeit von 1 ms wird er auch damit nicht kommen. Es gab schonmal hier eine Diskussion darüber. Ich habe mal früher einbisschen mit SDI.NET rumgespielt. Dort laufen alle Aktionen event-basiert ab wenn ich mich richtig erinnere. Eine Gameloop in diesem Sinne gab es nicht.

Gruss
tscherno

To understand recursion you must first understand recursion

http://www.ilja-neumann.com
C# Gruppe bei last.fm

5.658 Beiträge seit 2006
vor 15 Jahren

Meine Timer-Klasse erzeugt einen TimeStamp in Millisekunden als double zurück und ist nanosekundengenau:

    /// <summary>
    /// The Timer class provides a very exact System Timer which
    /// works with nanosecond precision. 
    /// </summary>
    public class Timer
    {
 
        /// <summary>
        /// Query performance (high resolution) timer frequency.
        /// </summary>
        /// <param name="lpFrequency">The current frequency in lpFrequency.</param>
        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("Kernel32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool QueryPerformanceFrequency(
            out long lpFrequency);


        /// <summary>
        /// Query performance (high resolution) timer counter.
        /// </summary>
        /// <param name="lpCounter">The current counter value in lpCounter.</param>
        [System.Security.SuppressUnmanagedCodeSecurity]
        [DllImport("Kernel32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool QueryPerformanceCounter(
            out long lpCounter);


        /// <summary>
        /// Returns the factor to convert the time returned
		/// by the <see cref="QueryPerformanceCounter(out long)"/>
		/// method to ms.
        /// </summary>
        /// <returns>Returns the PerformanceFrequency.</returns>
        private static double GetQueryPerformanceFrequency()
        {
            long f;
            QueryPerformanceFrequency(out f);
            return 1000.0 / (double)f;
        }  


        /// <summary>
        /// Contains the PerformanceFrequency.
        /// </summary>
        private static double freqFactor = GetQueryPerformanceFrequency();


        /// <summary>
        /// Returns the current time in ms.
        /// </summary>
        /// <returns>Returns the current time in ms.</returns>
        public static double GetTimeStamp()
        {
            long c;
            QueryPerformanceCounter(out c);

            return (c * freqFactor);
        }  

    }

Weeks of programming can save you hours of planning

E
Easyrider Themenstarter:in
200 Beiträge seit 2006
vor 15 Jahren

Hallo,

danke für die vielen Antworten, ihr habt mir sehr geholfen. Das freut mich 👍

@MrSparkle: Ich werde deine Klasse sobald sich die Zeit dazu ergibt testen. Dankeschon dafür!

mfg

Easy

O
778 Beiträge seit 2007
vor 15 Jahren

@EasyRider:

Ich würde als erstes mal die Zeile

diff = end.Millisecond - start.Millisecond;

mit

diff = (end - start).TotalMilliseconds;

ersetzen und diff double deklarieren. Grund: Sei start von der Zeit her 1s 980ms und end 2s 30ms, dann sollte bei diff dann 30-980, also -950s drinstehen.

@MrSparkle:

Hm, ich würde an deiner Stelle noch freqFactor in einer Property zugänglich machen, weil es mal interessant zu wissen ist, in welcher Genauigkeit das System so arbeitet. Bei mir (Amd-Dualcorelaptop mit 32bit Vista Sp1) kam eine Genauigkeit von 7*10^-8 s raus - was zwar noch lange nicht nanosekundengenau ist, aber mit einer Genauigkeit von 70ns kann man ganz gut leben glaub' ich. Wäre nur noch die Frage wie zuverlässig die Genauigkeit hinsichtlich ihrer Konstanz ist. Du gehst jetzt einfach davon aus, dass sie das ist, ich frage mich aber, ob du das so einfach machen kannst.

E
Easyrider Themenstarter:in
200 Beiträge seit 2006
vor 15 Jahren

Hallo,

deine Klasse funktioniert super MrSparkle! Hast mir sehr viel Arbeit damit gerettet. Dankeschön!

@onlinegurke: Sowas in der Art habe ich eingebaut, hab es nur für dieses eine Beispiel rausgenommen, um den Code zu vereinfachen. Aber danke für den Hinweis 😉

Zur Zuverlässigkeit: Bis jetzt hatte ich nie mehr als 100 ms Unterschied auf 60 Sekunden. Da dies vollkommen ausreichend für meine Applikation ist, werde ich es bei dieser Klasse belassen.

Vielen Dank an alle, die gelesen und geholfen haben.

mfg

Easy

*edit: Fehlerteufel, der du bist auf Erden...

5.658 Beiträge seit 2006
vor 15 Jahren

@easyrider:

Bitte!

@onlinegurke:

Hm, ich würde an deiner Stelle noch freqFactor in einer Property zugänglich machen, weil es mal interessant zu wissen ist, in welcher Genauigkeit das System so arbeitet. Bei mir (Amd-Dualcorelaptop mit 32bit Vista Sp1) kam eine Genauigkeit von 7*10^-8 s raus - was zwar noch lange nicht nanosekundengenau ist, aber mit einer Genauigkeit von 70ns kann man ganz gut leben glaub' ich. Wäre nur noch die Frage wie zuverlässig die Genauigkeit hinsichtlich ihrer Konstanz ist. Du gehst jetzt einfach davon aus, dass sie das ist, ich frage mich aber, ob du das so einfach machen kannst.

Wäre schon ziemlich interessant, aus theoretischem Interesse. Aber praktisch mache ich mir über Nanosekunden kaum Gedanken. Da benötigt der Aufruf der TimeStamp-Funktion wahrscheinlich mehr Rechenzeit. Bei meinen Anwendungen reicht mir eine Genauigkeit von 10ms.

Weeks of programming can save you hours of planning

X
1.177 Beiträge seit 2006
vor 15 Jahren

huhu ,

Es geht in diesem Stück darum, das einfachste Grundgerüst für eine grafische Engine zu erstellen. Das hier ist der Teil mit der Framebremse auf 25 FPS.

Ich halte dein Vorgehen leider für grundlegend falsch. Du verbrätst in einer Schleife Rechenzeit, nur weil zu viel davon da ist.

Prinzipiell müsstest Du ja eh mit Threads arbeiten um sonstige Arbeit zu berechenen. Warum dann nicht das "warten" auch in einen Thread auslagern und per Event dann nach einem Sleep(39) (oder so) bescheid geben, dass es soweit ist das ganze auf den Bildschirm zu bringen? Desweiteren denke ich, dass Spiele die anhand der Framerate etwas berechnen nicht mehr Zeitgemäß sind.

Nehmen wir mal den beliebten Rocket-Jump von Quake: Hier wurde von Frame zu Frame der Bewegungsvektor berechnet, was zu fehlerhaften/unterschiedlichen Resultaten geführt hat. Eigentlich müsste man die Position des bewegten Objektes nach X Zeiteinheiten berechnen, da die Flugkurve ja nur von ein paar Faktoren abhängig ist.

🙂

Xynratron

Herr, schmeiss Hirn vom Himmel - Autsch!

Die Erfahrung zeigt immer wieder, dass viele Probleme sich in Luft auslösen, wenn man sich den nötigen Abstand bzw. Schlaf gönnt.

E
Easyrider Themenstarter:in
200 Beiträge seit 2006
vor 15 Jahren

Hallo Xynratron,

Du verbrätst in einer Schleife Rechenzeit, nur weil zu viel davon da ist. Mach ich nur indirekt, da meine richtige Schleife derzeit nicht mehr als Application.DoEvents() macht solange sie wartet.

Prinzipiell müsstest Du ja eh mit Threads arbeiten Habe ich vor, aber erst in einer späteren Version. Derzeit möchte ich noch keine Threads einbauen, da es sonst zu kompliziert werden würde. Vor allem alles invoken, threadsafe arbeiten, etc etc. Der Overhead für zwischendurch ist mir einfach zu hoch.

Und zum "nicht mehr zeitgemäßen" etwas mehr Theorie:

In meiner Applikation kann man ein Objekt (z. B. eine Person) auf eine Picturebox hinzufügen. Danach wird eine Art Waypoint-System per Drag&Drop zu diesem Objekt hinzugefügt. Mit meiner "Grafikengine" möchte ich nun, das die Person die Waypoints in immer der gleichen Geschwindigkeit "abläuft".

Soweit zur Theorie. Den einzigen Lösungsweg, den ich kenne, ist der den ich benutze. Ich berechne im Frame die Position der Person auf dem Vektor des Waypoints und zeichne sie. Um das gleichmäßig hinzubekommen, arbeite ich mit Framebremsen und Einheitsvektoren sowie Steigungen... Hast du bessere Ideen wie man das umsetzen kann? Bzw. ein gutes Beispiel zu dem bereits von dir genannten, das man mit meinem Vergleichen kann? GEgen eine bessere Lösung, die nicht allzu Zeitaufwändig ist, hätte ich nix einzuwenden.

mfg

Easy