Java timing accuracy on Windows XP vs. Windows 7
Asked Answered
R

3

9

I have a bizarre problem - I'm hoping someone can explain to me what is happening and a possible workaround. I am implementing a Z80 core in Java, and attempting to slow it down, by using a java.util.Timer object in a separate thread.

The basic setup is that I have one thread running an execute loop, 50 times per second. Within this execute loop, however many cycles are executed, and then wait() is invoked. The external Timer thread will invoke notifyAll() on the Z80 object every 20ms, simulating a PAL Sega Master System clock frequency of 3.54 MHz (ish).

The method I have described above works perfectly on Windows 7 (tried two machines) but I have also tried two Windows XP machines and on both of them, the Timer object seems to be oversleeping by around 50% or so. This means that one second of emulation time is actually taking around 1.5 seconds or so on a Windows XP machine.

I have tried using Thread.sleep() instead of a Timer object, but this has exactly the same effect. I realise granularity of time in most OSes isn't better than 1ms, but I can put up with 999ms or 1001ms instead of 1000ms. What I can't put up with is 1562ms - I just don't understand why my method works OK on newer version of Windows, but not the older one - I've investigated interrupt periods and so on, but don't seem to have developed a workaround.

Could anyone please tell me the cause of this problem and a suggested workaround? Many thanks.

Update: Here is the full code for a smaller app I built to show the same issue:

import java.util.Timer;
import java.util.TimerTask;

public class WorkThread extends Thread
{
   private Timer timerThread;
   private WakeUpTask timerTask;

   public WorkThread()
   {
      timerThread = new Timer();
      timerTask = new WakeUpTask(this);
   }

   public void run()
   {
      timerThread.schedule(timerTask, 0, 20);
      while (true)
      {
         long startTime = System.nanoTime();
         for (int i = 0; i < 50; i++)
         {
            int a = 1 + 1;
            goToSleep();
         }
         long timeTaken = (System.nanoTime() - startTime) / 1000000;
         System.out.println("Time taken this loop: " + timeTaken + " milliseconds");
      }
   }

   synchronized public void goToSleep()
   {
      try
      {
         wait();
      }
      catch (InterruptedException e)
      {
         System.exit(0);
      }
   }

   synchronized public void wakeUp()
   {
      notifyAll();
   }

   private class WakeUpTask extends TimerTask
   {
       private WorkThread w;

       public WakeUpTask(WorkThread t)
       {
          w = t;
       }

       public void run()
       {
          w.wakeUp();
       }
   }
}

All the main class does is create and start one of these worker threads. On Windows 7, this code produces a time of around 999ms - 1000ms, which is totally fine. Running the same jar on Windows XP however produces a time of around 1562ms - 1566ms, and this is on two separate XP machines that I have tested this. They are all running Java 6 update 27.

I find this problem is happening because the Timer is sleeping for 20ms (quite a small value) - if I bung all the execute loops for a single second into wait wait() - notifyAll() cycle, this produces the correct result - I'm sure people who see what I'm trying to do (emulate a Sega Master System at 50fps) will see how this is not a solution though - it won't give an interactive response time, skipping 49 of every 50. As I say, Win7 copes fine with this. Sorry if my code is too large :-(

Radiator answered 26/9, 2011 at 17:12 Comment(11)
Interesting question. Would it be possible for you to provide a self-contained code snippet that exhibits this behaviour?Jute
I can give you the sourcecode of the Timer if you like? I'd give you the lot but the Z80 core is shaping up to be quite hefty ;-)Radiator
@aix "self-contained code snippet" Self contained code may be short, but it cannot (by definition) be a snippet. I recommend posting an SSCCE.Tweed
What a sweet project! Z-80 is one of the all-time great machines. It really kicked i8085's ass when it came out. My fave CPU, no exceptions. If you ever have trouble, shoot me an email (see profile); I'd love to help.Siderosis
@AndrewThompson: I think what SO is missing is a Nitpicker badge. We all know what I meant. ;-)Jute
@aix "@AndrewThompson: I think what SO is missing is a Nitpicker badge." My name has a space. ..Do I get 2 of those badges now? :PTweed
@AndrewThompson: You do. The space gets automatically removed by SO's tab completion. Try it. ;-)Jute
The change in behavior with different OSes could possibly be due to a change in timer source. There are various hardware timers available to the OS and some may be more accurate than others (HPET based timers are a relatively new development, earlier timing sources relied on older interrupt controller).Ardelia
Possibly related: #3275392Sordello
Hi ritesh I tried using a /usepmtimer switch in boot.ini to force more reliable timer behaviour. I also tried the daemon thread with a Thread.sleep(Long.MAX_VALUE) in it to start before anything else, but neither of them had any effect. I have posted the code for a much simpler application to show what I mean - this exhibits the same problem. Pete - thanks very much, I'm sure I will hit plenty of trouble in the course of my work ;-) I am doing it for a challenge really.Radiator
I've just read somewhere that the standard timer resolution on older versions of windows is either 10ms or 15.62ms, depending on the interrupt period of the system - I don't know valid this is, but the fact that I'm getting a real speed of 1.562 seconds for 1 second of Z80 time certainly raised my eyebrow. I am unsure how to change this though - the Thread.sleep(Long.MAX_VALUE) certainly didn't make a difference, unless I was doing it incorrectly somehow - does anyone have an example of using this method to change the timer resolution to 1ms on Windows XP? Many thanks.Radiator
E
5

Could anyone please tell me the cause of this problem and a suggested workaround?

The problem you are seeing probably has to do with clock resolution. Some Operating Systems (Windows XP and earlier) are notorious for oversleeping and being slow with wait/notify/sleep (interrupts in general). Meanwhile other Operating Systems (every Linux I've seen) are excellent at returning control at quite nearly the moment specified.

The workaround? For short durations, use a live wait (busy loop). For long durations, sleep for less time than you really want and then live wait the remainder.

Enthronement answered 26/9, 2011 at 23:5 Comment(5)
Just like to add, when using loops, use System.nanoTime() rather than System.currentTimeMillis() since currentTimeMillis() has the same granularity as Thread.sleep(). Link to additional discussion.Flatfish
Is there not a way to set the clock resolution down to 1ms on XP? I was under the impression that there was - but obviously as I have not managed to do so I may be wrong ;-)Radiator
Sorry, no. It is an OS thing. One of those hidden ways in which a Java program can unintentionally become platform specific.Enthronement
OK thank you anyway for your answer Tim, and for everyone else :-) I think I will just use the timer solution I already have on more modern OSes, as it works perfectly - I will code it to fall back to a busy-wait loop when the timing is off by more than a reasonable amount :-) Getting there - I've implemented about 14 opcodes now...only a few hundred to go ;-)Radiator
@PhilPotter1987, On my drive to work it occurred to me that you might get different results with a ScheduledExecutorService, this is the Java 5 replacement for Timer anyway. Another possibility is Condition#awaitNanos. Neither address clock resolution, but the implementations might account for it (though I doubt it). You might also want to wiki spurious wakeup, some threading libraries which a JVM implementation may use could cause a thread to wake up absent of a signal. I've yet to hear of someone encountering this though.Enthronement
V
2

I'd forgo the TimerTask and just use a busy loop:

long sleepUntil = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(20);
while (System.nanoTime() < sleepUntil) {
    Thread.sleep(2); // catch of InterruptedException left out for brevity
}

The two millisecond delay gives the host OS plenty of time to work on other stuff (and you're likely to be on a multicore anyway). The remaining program code is a lot simpler.

If the hard-coded two milliseconds are too much of a blunt instrument, you can calculate the required sleep time and use the Thread.sleep(long, int) overload.

Vibrator answered 26/9, 2011 at 20:24 Comment(1)
Thanks for this Barend - I hadn't considered this. I tried it and it is much closer to my required time range - still not enough though. I still have an oversleep of about 90ms on average, even when calculating the required sleep time and using Thread.sleep(long, int). As I said I don't mind being off by one or two milliseconds but this is too much. Thank you for the suggestion though - it is a good one and when I get home from work and try this on Windows 7 I have no doubt it will work well - it still does not answer why this problem exists though.Radiator
T
1

You can set the timer resolution on Windows XP.

http://msdn.microsoft.com/en-us/library/windows/desktop/dd757624%28v=vs.85%29.aspx

Since this is a system-wide setting, you can use a tool to set the resolution so you can verify whether this is your problem.

Try this out and see if it helps: http://www.lucashale.com/timer-resolution/

You might see better timings on newer versions of Windows because, by default, newer version might have tighter timings. Also, if you are running an application such as Windows Media Player, it improves the timer resolution. So if you happen to be listening to some music while running your emulator, you might get great timings.

Tatar answered 22/3, 2012 at 14:31 Comment(2)
Thanks for this - my emulator is specifically designed to be cross-platform though, and so can't rely on Windows specifics such as this to get good timing resolution. In the end I used a sleeping thread, with code to fall back to a busy wait loop if timing is out by more than 1ms in every 20ms. Seems to work quite well on XP now.Radiator
Phil - That sounds like a good multi-platform solution without having to write special case code for each platform. NiceTatar

© 2022 - 2024 — McMap. All rights reserved.