Untersuchung der optimalen Schlaf-Zeit-Berechnung im Spiel-Schleife
Wenn die Programmierung von Animationen und kleine Spiele, die ich bin gekommen, um zu wissen, die unglaubliche Bedeutung der Thread.sleep(n);
verlasse ich mich auf diese Methode, um das Betriebssystem anweisen, wenn meine Anwendung nicht benötigen, CPU, und mit diesem macht mein Programm die Fortschritte, die in vorhersehbarer Geschwindigkeit.
Mein problem ist, dass die JRE verwendet verschiedene Methoden der Implementierung dieser Funktionalität auf verschiedenen Betriebssystemen. Auf UNIX-basierten (oder beeinflusst) OS:es-wie Ubuntu und OS X, die zugrunde liegenden JRE Implementierung verwendet werden, eine gut funktionierende und präzises system für die Verteilung von CPU-Zeit für verschiedene Anwendungen, und so macht mir mein 2D-Spiel glatt und lag-frei. Jedoch auf Windows 7 und ältere Microsoft-Systemen, die CPU-Zeit-Verteilung scheint zu funktionieren unterschiedlich, und Sie erhalten in der Regel wieder Ihre CPU-Zeit, die nach der gegebenen Menge an Schlaf, variierend mit etwa 1-2 ms vom Ziel schlafen. Jedoch bekommen Sie gelegentliche Ausbrüche von extra 10-20 ms der Zeit schlafen. Dies bewirkt, dass mein Spiel lag einmal alle paar Sekunden, wenn dies geschieht. Ich habe bemerkt, dass dieses problem existiert auf den meisten Java-Spiele, die ich habe versucht, auf Windows, Minecraft wird ein spürbares Beispiel.
Nun, ich habe auf der Suche im Internet um eine Lösung zu finden für dieses problem. Ich habe gesehen, eine Menge Leute mit nur Thread.yield();
statt Thread.sleep(n);
, die funktioniert einwandfrei auf Kosten der aktuell verwendeten CPU-Kern immer Volllast, egal, wie viel CPU-Ihr Spiel auch tatsächlich braucht. Dies ist nicht ideal für das Spiel auf laptops oder high-Energie-Verbrauch-workstations, und es ist ein unnötiger trade-off auf Macs und Linux-Systeme.
Suchen, um weiter fand ich eine weit verbreitete Methode der Korrektur Zeit schlafen Inkonsistenzen namens "spin-Schlaf", wobei Sie nur um den Schlaf, für 1 ms zu einer Zeit und überprüfen Sie für die Konsistenz mit der System.nanoTime();
Methode, die sehr genau ist, auch auf Microsoft-Systemen. Dies hilft, die für den normalen 1-2 ms der Schlaf Ungereimtheit, aber es wird nicht helfen gegen die gelegentlichen Ausbrüche von +10-20 ms Schlaf Inkonsistenz, da dies oft dazu führt, dass mehr Zeit verbracht, als einen Zyklus von meiner Schleife sollten alle zusammen.
Nach Tonnen zu suchen, fand ich diese kryptischen Artikel von Andy Malakov, die war sehr hilfreich bei der Verbesserung meiner Schleife: http://andy-malakov.blogspot.com/2010/06/alternative-to-threadsleep.html
Basiert auf seinem Artikel schrieb ich diese sleep-Methode:
//Variables for calculating optimal sleep time. In nanoseconds (1s = 10^-9ms).
private long timeBefore = 0L;
private long timeSleepEnd, timeLeft;
//The estimated game update rate.
private double timeUpdateRate;
//The time one game loop cycle should take in order to reach the max FPS.
private long timeLoop;
private void sleep() throws InterruptedException {
//Skip first game loop cycle.
if (timeBefore != 0L) {
//Calculate optimal game loop sleep time.
timeLeft = timeLoop - (System.nanoTime() - timeBefore);
//If all necessary calculations took LESS time than given by the sleepTimeBuffer. Max update rate was reached.
if (timeLeft > 0 && isUpdateRateLimited) {
//Determine when to stop sleeping.
timeSleepEnd = System.nanoTime() + timeLeft;
//Sleep, yield or keep the thread busy until there is not time left to sleep.
do {
if (timeLeft > SLEEP_PRECISION) {
Thread.sleep(1); //Sleep for approximately 1 millisecond.
}
else if (timeLeft > SPIN_YIELD_PRECISION) {
Thread.yield(); //Yield the thread.
}
if (Thread.interrupted()) {
throw new InterruptedException();
}
timeLeft = timeSleepEnd - System.nanoTime();
}
while (timeLeft > 0);
}
//Save the calculated update rate.
timeUpdateRate = 1000000000D / (double) (System.nanoTime() - timeBefore);
}
//Starting point for time measurement.
timeBefore = System.nanoTime();
}
SLEEP_PRECISION
Ich setzen in der Regel etwa 2 ms, und die SPIN_YIELD_PRECISION
etwa 10 000 ns für beste performance auf meinem Windows 7-Maschine.
Nach Tonnen von harter Arbeit, das ist der absolute beste, die ich mit oben kommen kann. So, da ich noch Sorge um die Verbesserung der Genauigkeit von diesem Schlaf-Methode, und ich bin immer noch nicht zufrieden mit der performance, ich möchte den Appell an Sie alle, die Sie java game Hacker und Animatoren gibt Vorschläge für eine bessere Lösung für die Windows-Plattform. Könnte ich ein Plattform-spezifische Art und Weise auf Windows, es besser zu machen? Ich kümmern sich nicht darum, eine kleine Plattform-spezifischen code für meine Anwendungen, solange der Großteil des Codes ist OS unabhängig.
Ich würde auch gerne wissen, ob es jemanden gibt, der weiß, über Microsoft und Oracle arbeiten, eine bessere Umsetzung der Thread.sleep(n);
Methode, oder was die Oracle-Pläne für die Zukunft auf die Verbesserung Ihrer Umwelt als Grundlage der Anwendungen, die hohe timing-Genauigkeit, wie Musik, software und Spiele?
Danke Euch allen für das Lesen meiner langen Frage/Artikel. Ich hoffe einige Leute finden meine Forschung hilfreich!
- Hvae Sie versucht, indem Sie geplante tasks, um eine ScheduleExecutorService. Diese erlauben es, bestimmte Aufgabe je 1 ms oder ziemlich nah dran.
- Sind Sie sicher, dass die
10-20 ms of sleep time
ist, was die Ursache der Verzögerung? Selbst bei 50FPS, das ist nur ein einziges Bild, das verworfen wird, oder solange verzögert, bis sein Nachfolger. Vielleicht ist es die Formel, die Sie berechnen die Positionen Ihrer sprites statt. - Lawrey - tatsächlich, in einigen Situationen ScheduledExecutorService ausführen kann schlimmer geworden, seit es nutzt die nano-mal-api. wir haben Situationen erlebt, in denen die nano-mal Zeug war völlig unzuverlässig unter windows (vor allem, wenn die Ausführung als virtuelle Gäste).
- Nein, ich bin nicht wirklich tot sicher. Ich habe mein bestes versucht, mit verschiedenen benchmarks und versuchen pin nach dem problem, aber ich habe nicht in der Lage, um herauszufinden, eine ganze Menge. Nach Barfieldmv die Antwort, das lag vielleicht tatsächlich schlimmer, als ich dachte. Derzeit bin ich eigentlich nur zeichnen von gefüllten Rechtecken, also ich glaube nicht, dass ich irgendein problem mit meinem sprite-Algorithmus, da es keines gibt.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Könnte man eine cyclic-timer im Zusammenhang mit einer mutex. Das ist IHMO der effizienteste Weg, das zu tun, was Sie wollen. Aber dann sollte man sich überlegen, zum überspringen von frames, im Fall der computer lag (Sie können es mit anderen, nicht blockierende mutex in der timer-code.)
Edit: Einige pseudo-code zu klären,
Timer-code:
Schlafen-code:
Startwerte:
Bearbeiten: korrigiert Initialisierung Werte.
Den folgenden code arbeiten ziemlich gut auf windows (Schleifen genau 50fps mit einer Präzision auf die Millisekunde genau)
animationTimer.schedule(new GameLoop(), 0, 12);
ich weiß, das ist nicht ganz alle Sachen, die Sie mich gebeten, zu tun, aber es sollte kein problem sein, solange mein computer ist sehr schnell, und das Programm nimmt sehr wenig Zeit zum ausführen (jedes Spiel loop-Zyklus dauert weniger als 1 ms, so wird die Schleife nie mehr Zeit als 12 ms). Ich habe die gleiche lag platzt hier habe ich mit meinem eigenen code. Vielleicht ist der scheduler wird implementiert unter Verwendung vonThread.sleep()
?Ihre Möglichkeiten begrenzt sind, und Sie hängt davon ab, was genau Sie wollen zu tun. Ihr code-snippet ein, erwähnt die max FPS, aber die max FPS würde erfordern, dass Sie nie mehr schlafen, also bin ich mir nicht ganz sicher, was Sie beabsichtigen, mit, dass. Keine Schlaf-oder yield-überprüfung wird keinen Unterschied machen in den meisten problem-Situationen jedoch - wenn eine andere app ausgeführt werden muss jetzt, und das OS nicht wechseln wollen bald zurück, es ist egal welche von denen, die Sie aufrufen, bekommen Sie die Kontrolle zurück, wenn das OS entscheidet, so zu tun, die werden sicherlich mehr als 1ms in die Zukunft. Aber das OS kann sicherlich einmal in die macht wechselt häufiger - Win32 hat die timeBeginPeriod Anruf für genau diesen Zweck, die Sie verwenden können, irgendwie. Aber es ist ein guter Grund für die nicht wechseln zu oft -, es ist weniger effizient.
Die beste Sache zu tun, die zwar etwas komplexer wird, wird in der Regel gehen für ein Spiel Schleife, die nicht erfordern Echtzeit-updates, sondern führt die logic-updates in festen Zeitabständen (zB. 20x pro Sekunde) und macht, wenn möglich (vielleicht mit beliebigen kurze schläft zu befreien CPU für andere Anwendungen, die, wenn Sie nicht im full-screen -). Durch die Pufferung einer vergangenen Logik-Zustand sowie die aktuellen können Sie interpolieren zwischen Ihnen, um die rendering erscheinen, so glatt, als ob Sie Taten Logik jedes mal aktualisiert, wenn. Für mehr Informationen zu diesem Ansatz, können Sie sehen, die Fixieren Sie Ihre Zeitschritt Artikel.
Nein, das wird nicht passieren. Denken Sie daran, der Schlaf ist nur eine Methode sagen, wie lange Sie wollen, Ihr Programm eingeschlafen. Ist es nicht eine Spezifikation, für, wenn Sie wird oder sollte aufwachen, und nie sein wird. Per definition ist jedes system, das mit sleep und yield Funktionen ist ein multitasking-system, wo die Anforderungen anderer Aufgaben betrachtet werden, und das Betriebssystem wird immer der Letzte Aufruf auf die Terminierung dieser. Die alternative wäre nicht zuverlässig funktioniert, weil wenn ein Programm irgendwie könnte die Nachfrage reaktiviert werden, auf einen genauen Zeitpunkt seiner Wahl, könnte es verhungern andere Prozesse an CPU-Leistung. (zB. Ein Programm, das erzeugt einen hintergrund-thread und hatte beide threads Durchführung von 1ms der Arbeit und Aufruf von sleep(1) am Ende könnte abwechselnd hog einem CPU-Kern.) So, für einen user-space-Programm, schlafen (und Funktionalität, wie es ist) immer eine untere Schranke, die nie eine Obere Schranke. Zu tun besser als die, die erfordert, dass das Betriebssystem selbst es, bestimmte apps ziemlich viel eigene Planung, und dies ist nicht ein wünschenswertes feature in Betriebssystemen, die für consumer-hardware (während einer gemeinsamen und nützliche Funktion für industrielle Anwendungen).
timeLoop
(die Zeit, die ein Zyklus der Schleife nehmen sollte) wird berechnet, indem eine zweite mit der gewünschten FPS (meistens 60).Timing-Zeug ist notorisch schlecht unter windows. Dieser Artikel ist ein guter Ort, um zu starten. Nicht sicher, wenn Sie kümmern, aber beachten Sie auch, dass es schlimmere Probleme (vor allem mit System.nanoTime) auf virtuellen Systemen (bei windows ist das Gast-Betriebssystem).
Thread.Schlaf sagt, dass man sich die app braucht keine Zeit mehr. Dies bedeutet, dass in einem worst-case-Szenario, müssen Sie warten, für eine ganze Gewinde schneiden (40ms oder so).
Nun in schlimmen Fällen, wenn ein Treiber oder etwas längere Zeit in Anspruch nimmt, könnte es sein, Sie müssen warten, bis 120ms (3*40ms) so Thread.Schlaf ist nicht der Weg zu gehen. Einen anderen Weg gehen, wie die Registrierung eines 1ms-Rückruf und starten von draw-code sehr X-callbacks.
(Dies ist unter windows, würde ich die Verwendung von MultiMedia-tools, um diese 1ms Auflösung Rückrufe)
Thread.sleep
ist ungenau und macht die animation nervös, die meisten der Zeit.Wenn Sie ersetzen Sie vollständig mit
Thread.yield
erhalten Sie eine solide FPS, ohne Verzögerung oder jitter, jedoch ist die CPU-Auslastung steigt stark. Zog ich den Thread.Ertrag vor langer Zeit.Dieses problem diskutiert wurde, auf die Java-Spiele-Entwicklung-Foren seit Jahren.