2011-09-26 11 views
9

Mam dziwny problem - mam nadzieję, że ktoś może mi wyjaśnić, co się dzieje i możliwe obejście problemu. Implementuję rdzeń Z80 w Javie i próbuję go spowolnić, używając obiektu java.util.Timer w osobnym wątku.Dokładność synchronizacji czasu Java w systemie Windows XP i Windows 7

Podstawową konfiguracją jest to, że mam jeden wątek z uruchomioną pętlą, 50 razy na sekundę. W obrębie tej pętli wykonania wykonuje się wiele cykli, a następnie wywoływane jest wait(). Zewnętrzny wątek Timer wywoła funkcję notifyAll() na obiekcie Z80 co 20ms, symulując częstotliwość taktowania systemu PAL Sega Master System równą 3,54 MHz (ish).

Opisana powyżej metoda działa doskonale w systemie Windows 7 (wypróbowano dwie maszyny), ale wypróbowałem również dwa komputery z systemem Windows XP i na obu z nich obiekt Timer wydaje się być zaspany o około 50%. Oznacza to, że jedna sekunda czasu emulacji trwa około 1,5 sekundy na komputerze z systemem Windows XP.

Próbowałem użyć Thread.sleep() zamiast obiektu Timer, ale ma to dokładnie taki sam efekt. Zdaję sobie sprawę, że czasowość w większości systemów operacyjnych nie jest lepsza niż 1ms, ale mogę wytrzymać 999ms lub 1001ms zamiast 1000ms. Nie mogę znieść 1562ms - po prostu nie rozumiem, dlaczego moja metoda działa poprawnie w nowszej wersji systemu Windows, ale nie starszej - badałem okresy przerwania itd., Ale nie wydaje mi się opracowali obejście.

Czy ktoś mógłby mi powiedzieć, jaka jest przyczyna tego problemu i sugerowane obejście tego problemu? Wielkie dzięki.

Aktualizacja: Oto pełny kod dla mniejszych app zbudowany, aby pokazać ten sam problem:

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(); 
     } 
    } 
} 

wszystkie główne klasy nie jest stworzyć i uruchomić jeden z tych wątków roboczych. W systemie Windows 7 ten kod generuje czas około 999ms - 1000ms, co jest całkowicie w porządku. Uruchamianie tego samego słoika w systemie Windows XP daje jednak czas około 1562 ms - 1566 ms, a to jest na dwóch osobnych maszynach XP, które przetestowałem. Wszystkie są uruchomione Java 6 aktualizacja 27.

Uważam, że ten problem ma miejsce, ponieważ Timer śpi przez 20 ms (dość mała wartość) - jeśli przelałbym wszystkie pętle wykonywania na jedną sekundę w oczekiwanie oczekiwania() - cykl notifyAll(), to daje prawidłowy wynik - jestem pewien, że ludzie, którzy zobaczą, co próbuję zrobić (emulować system Sega Master przy 50 klatkach na sekundę) zobaczą, jak to nie jest rozwiązaniem - to nie da Interaktywny czas reakcji, pomijanie 49 na 50. Jak mówię, Win7 radzi sobie z tym dobrze. Przepraszam, jeśli mój kod jest zbyt duża :-(

+0

Interesujące pytanie. Czy możliwe byłoby podanie niezależnego fragmentu kodu, który wykazuje takie zachowanie? – NPE

+0

Mogę podać kod źródłowy zegara, jeśli chcesz? Dałbym ci dużo, ale rdzeń Z80 kształtuje się dość mocno ;-) – PhilPotter1987

+1

@ aix * "samodzielny fragment kodu" * Samodzielny kod może być krótki, ale nie może (z definicji) być [ snippet] (http://en.wikipedia.org/wiki/Snippet_%28programming%29). Polecam opublikowanie [SSCCE] (http://pscode.org/sscce.html). –

Odpowiedz

5

Czy ktoś mógłby mi powiedzieć, jaka jest przyczyna tego problemu i sugerowane obejście tego problemu?

Problem, który widzisz prawdopodobnie ma związek z clock resolution. Niektóre systemy operacyjne (Windows XP i wcześniejsze) są znane z zaspokojenia i spowolnienia z oczekiwaniem/powiadomieniem/uśpieniem (ogólnie przerwania).W międzyczasie inne systemy operacyjne (każdy system Linux, który widziałem) są doskonałe do przywracania kontroli w dość zbliżonym momencie.

Sposób obejścia? W przypadku krótkich okresów używaj trybu oczekiwania na żywo (pętla zajętości). Przez dłuższy czas śpij mniej, niż naprawdę chcesz, a następnie przeżyj resztę.

+1

Podobnie jak do dodawania, podczas korzystania z pętli, należy użyć raczej opcji [System.nanoTime()] (http://download.oracle.com/javase/6/docs/api/java/lang/System.html#nanoTime()) niż [System.currentTimeMillis()] (http://download.oracle.com/javase/6/docs/api/java/lang/System.html#currentTimeMillis()), ponieważ currentTimeMillis() ma taką samą szczegółowość jak wątek. sen(). [Link] (http://stackoverflow.com/questions/351565/system-currenttimemillement-vs-system-nanotime) do dodatkowej dyskusji. – prunge

+0

Czy nie ma sposobu, aby ustawić rozdzielczość zegara na 1 ms w systemie XP? Miałem wrażenie, że tak było - ale oczywiście, ponieważ nie udało mi się tego zrobić, mogę się mylić ;-) – PhilPotter1987

+0

Niestety, nie. To jest rzecz OS. Jeden z tych ukrytych sposobów, w których program Java może niechcący stać się platformą. –

2

bym zrezygnować z TimerTask i po prostu korzystać z ruchliwą pętlę:

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

Dwa opóźnienie milisekundy daje hosta OS dużo czasu, aby pracować na innych rzeczy (i tak prawdopodobnie będziesz w wielordzeniowym programie) Pozostały kod programu jest znacznie prostszy:

Jeśli zakodowane dwie sekundy są zbyt dużym tępym narzędziem, można obliczyć wymagany czas uśpienia i użyj przeciążenia Thread.sleep(long, int)

+0

Dzięki za ten Barend - nie zastanawiałem się nad tym. Próbowałem i jest znacznie bliższy mojemu wymaganemu zakresowi czasu - wciąż jednak nie dość. Ciągle spałem średnio około 90 ms, nawet przy obliczaniu wymaganego czasu snu i przy użyciu Thread.sleep (long, int). Jak już powiedziałem, nie mam nic przeciwko temu, by wyjechać o jedną lub dwie milisekundy, ale to za dużo.Dziękuję za sugestię - jest dobra i kiedy wracam do domu z pracy i spróbuję tego na Windows 7, nie mam wątpliwości, że to zadziała dobrze - wciąż nie odpowiada, dlaczego ten problem istnieje. – PhilPotter1987

1

Możesz może ustawić rozdzielczość zegara w systemie Windows XP.

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

Ponieważ jest to ustawienie całego systemu, można użyć narzędzia, aby ustawić rozdzielczość, dzięki czemu można sprawdzić, czy jest to Twój problem.

to wypróbować i zobaczyć, czy to pomaga: http://www.lucashale.com/timer-resolution/

Można zobaczyć lepsze czasy w nowszych wersjach systemu Windows, ponieważ domyślnie nowsza wersja może mieć ostrzejsze czasy. Ponadto, jeśli używasz aplikacji, takiej jak Windows Media Player, poprawia to rozdzielczość zegara. Jeśli więc słuchasz muzyki podczas działania emulatora, możesz uzyskać świetne odliczanie czasu.

+0

Dzięki za to - mój emulator został specjalnie zaprojektowany do pracy na wielu platformach, więc nie można polegać na takich specyfikacjach Windows, aby uzyskać dobrą rozdzielczość czasową. W końcu użyłem wątku do spania, z kodem powracającym do pętli oczekiwania, jeśli czas wygaśnie o więcej niż 1 ms na każde 20 ms. Wydaje się, że teraz działa całkiem dobrze na XP. – PhilPotter1987

+0

Phil - To brzmi jak dobre rozwiązanie wieloplatformowe bez konieczności pisania specjalnego kodu przypadków dla każdej platformy. Miły – dss539

Powiązane problemy