Timer vs. thread.sleep();

Started by
10 comments, last by N=1 11 years, 7 months ago
Thank you all for the input! I had the oportunity to speak to a friend of mine last night who programs for a living (not in game design.) He said the root of the problem is because of Java's garbage collect system, there really isn't a good way to create a real-time system in Java, as you can't know, or control when garbage collection will happen. (You can control it to some extent, by limiting the instantiation of objects where possible.) His opinion was that using a single thread, with a variable sleep durration would be the cleanest, and most consistent you could get, at least within Java. (Not accounting for tools available in other APIs.)
Advertisement

His opinion was that using a single thread, with a variable sleep durration would be the cleanest, and most consistent you could get, at least within Java. (Not accounting for tools available in other APIs.)


I'd recommend against it, Sleep does two things in Java:
1) It lets the underlying OS schedule another process or thread to run, your sleep duration is a minimum time for the OS to wait before considering your thread again (it can wait much, much longer and on a Desktop OS it frequently will) (on Android it doesn't apply, Android runs exactly one foreground process at the time and the scheduler will prioritize its threads which makes sleep accurate enough to use for this purpose (and on mobiles you really don't want to use more CPU than necessary since the battery is so small)
2) It triggers garbage collection (Which can also cause things to take longer than you'd expect).

The best way to avoid garbage collection is to:
1) Don't sleep in the main thread during gameplay and don't drop any references unless you can't avoid it. (This will prevent the GC from running most of the time)
2) Once you enter a loadscreen(or some other point where you can afford having the gc run), create an empty Object, create a weak reference to it then drop all references you wish to clean up, set the empty object to null then enter a loop that calls System.gc() followed by Thread.sleep(1) until calling .get() on the weak reference returns null (The weak reference will return null once the GC has destroyed the empty object you created).

The big problem with Sleep is not related to Java, it is related to desktop operating systems, the problem with sleeping in games occur regardless of what language you're using, you will always get a better gameplay experience if you hog the CPU(Which is what most games are doing). (The GC prefering to run while your thread is sleeping is actually a good thing with Java, the fact that you're sleeping means that you should be able to take the performance hit at that time (if you can't take a performance hit, don't sleep)

If you sleep on Windows(regardless of language) what will happen is:

1) Your thread stops running and the OS Scheduler starts, it will select another thread to run (out of those waiting for a turn on the CPU)
2) That thread will then run until a timer interrupt causes the scheduler to run again, on Windows this happens roughly every 16ms allthough you can force it to run more frequently with some native code (which isn't really recommended since it degrades overall system performance, more time spent scheduling rather than running applications) or until it decides to sleep. (Multicore cpus lets the OS schedule multiple threads to run at the same time which reduces the severity of this issue slightly but users also tend to have far more applications active in the background aswell)

This means that if a non sleeping thread gets scheduled after you call sleep your thread will have to wait atleast 16ms before it can run again, even if you ask to sleep for 1ms. in the worst case it won't pick your thread again the next time it runs either) and the delay can go up even higher.

and finally 3)
Your thread gets swapped back in, but since the CPU core your thread was using have been used by another thread almost nothing of what your thread was doing will be in the CPU cache which means you can look forward to some very expensive cache misses which further degrades performance. (Your thread might not even end up on the same CPU core as before the sleep).

So: for a game running in fullscreen, just hog the CPU , your game is the most important thing running on the system, add a powersaving option that the user can enable if they want to conserve their battery on a laptop or tablet. just put something like: if (powersavingEnabled) { Thread.sleep(1); } at the end of your game loop (a 1ms sleep per frame is usually enough to cause a modern CPU to clock down and save power) and then use an accurate system timer to decide if the update method should run or not.
The OS will still run the scheduler every ~16ms and might swap out your thread anyway but it will be far less common and on a multicore system the OS scheduler should be smart enough to leave a CPU hogging thread alone as long as it has other cores that it can run its less important stuff on)
[size="1"]I don't suffer from insanity, I'm enjoying every minute of it.
The voices in my head may not be real, but they have some good ideas!
Depending on your application and intended host system, it's actually ok to use sleep(). Sometimes it's unwise to do so, for instance if you're exhausting the cpu time of the system while wanting a higher framerate.
On the other hand, if you're certain that you don't need more game loop cycles than 1000/sleep_time_ms, you can go right ahead.
This will save battery and allow the os schedhuler to give other processes more cpu time.
For most fullscreen applications, the performance is so important that the main thread is never put to sleep().

As for the timing bit, keep that entirely seperate of sleep() . Don't rely on sleep() to set behaviour at an intended speed. Create a multiplier to all moving and animating things, which is set to the estimated framerate (avoiding spikes by smoothing and precalculating) relative to a preset, "denominating" framerate.

Mrs. Pac man made for 30fpson my machine yields approx 1730fps, so i would multiply the speed of all graphical change by 30/1730.

If you're making a multiplayer game, keep in sync with the host at specific intervals, yielding the multiplier which can then be interpolated up or down gradually.

I hope I'm making sense. :)
There is a great article that discusses how operating system clocks interact with Java methods, specifically System.currentTimeMillis(), Thread.sleep() and System.nanoTime(). The article claims that the Windows system clock (of modern Windows version) can trigger threads at 15 (16?) ms intervals, but can also be forced to work in 1ms intervals. I have not verified this, but I have seen System.currentTimeMillis() report jumps of 15 ms, instead of the expected 1 ms.

My personal experience is with the JGame library which uses an advanced version of the one that EpicWally suggests. (You can look up the main game loop is located in the JGEngine class in the JRE platformversion). One of the things they do is add a Thread.yield() in case there is no time left to sleep away with a Thread.sleep(). The demo games run very smoothly, but I guess you can hardly call them taxing.

Hogging the CPU with your main thread seems like an odd idea, since you will most likely be running at least a GUI thread next to your main game thread. You really do want the GUI thread to be able to paint your frame, and grab some input events while it's at it. Even worse: your OS can always interrupt your thread regardless of whether you call Thread.sleep() or Thread.yield(). Although I have not tested this, I would expect to have a smoother frame rate if you actively give up the cycle's you don't need to other processes. Rather than waiting for the OS to decide when it time for other processes to get a share of the pie (while you were in the middle of rendering a complex frame). In the presence of multiple cores this might play out differently, but I would not know of a way to have the GUI thread hog a core ;)

[size=2]Edit: minor erata

This topic is closed to new replies.

Advertisement