System.currentTimeMillis vs System.nanoTime

379

Dokładność vs. Precyzja

Chciałbym wiedzieć, czy powinienem używać System.currentTimeMillis () czy System.nanoTime () podczas aktualizacji pozycji mojego obiektu w mojej grze? Ich zmiana ruchu jest wprost proporcjonalna do czasu, jaki upłynął od ostatniego połączenia i chcę być tak precyzyjny, jak to możliwe.

Czytałem, że istnieją poważne problemy z rozdzielczością czasową między różnymi systemami operacyjnymi (mianowicie, że Mac / Linux ma rozdzielczość prawie 1 ms, podczas gdy Windows ma rozdzielczość 50ms ??). Głównie uruchamiam moje aplikacje w systemie Windows, a rozdzielczość 50 ms wydaje się dość niedokładna.

Czy są lepsze opcje niż dwie wymienione na liście?

Wszelkie sugestie / komentarze?

mmcdole
źródło
81
nanoTimejest zwykle znacznie bardziej dokładny niż currentTimeMillis, ale jest to również stosunkowo droga rozmowa. currentTimeMillis()działa na kilku (5-6) zegarach procesora, nanoTime zależy od podstawowej architektury i może wynosić ponad 100 zegarów procesora.
bestsss
10
Wszyscy zdajecie sobie sprawę, że system Windows generalnie ma ziarnistość przedziału czasu 1000ms / 64, prawda? Który wynosi 15.625 ms lub 15625000nanosekund!
7
Nie sądzę, że sto dodatkowych cykli zegara wpłynie na twoją grę, a kompromis prawdopodobnie byłby tego wart. Powinieneś wywoływać tę metodę tylko raz na aktualizację gry, a następnie zapisywać wartość w memie, aby nie dodawała ona zbyt dużego obciążenia. Jeśli chodzi o szczegółowość różnych platform, nie mam pojęcia.
aglassman
9
System Windows ma DOMYŚLNĄ ziarnistość przedziału czasu wynoszącą 1000 ms / 64. Możesz to zwiększyć poprzez natywny interfejs API timeBeginPeriod. Nowoczesne komputery mają także timery wysokiej rozdzielczości oprócz podstawowego timera. Timery o wysokiej rozdzielczości są dostępne poprzez wywołanie QueryPerformanceCounter.
Robin Davies
2
@Gohan - w tym artykule szczegółowo opisano wewnętrzne funkcjonowanie System.currentTimeMillis(): pzemtsov.github.io/2017/07/23/the-slow-currenttimemillis.html
Attila Tanyi

Odpowiedzi:

320

Jeśli szukasz bardzo precyzyjnych pomiarów upływającego czasu , użyj System.nanoTime(). System.currentTimeMillis()da ci najdokładniejszy możliwy upływ czasu w milisekundach od epoki, ale System.nanoTime()da ci nanosekundowy czas w stosunku do dowolnego dowolnego punktu.

Z dokumentacji Java:

public static long nanoTime()

Zwraca bieżącą wartość najbardziej precyzyjnego dostępnego zegara systemowego, w nanosekundach.

Metodę tę można stosować wyłącznie do pomiaru upływającego czasu i nie jest ona związana z żadnym innym pojęciem czasu systemowego ani zegara ściennego. Zwrócona wartość reprezentuje nanosekundy od pewnego ustalonego, ale dowolnego czasu początkowego (być może w przyszłości, więc wartości mogą być ujemne). Ta metoda zapewnia dokładność w nanosekundach, ale niekoniecznie w nanosekundach. Nie udziela się żadnych gwarancji dotyczących częstotliwości zmian wartości. Różnice w kolejnych połączeniach trwające dłużej niż około 292 lata (2 63 nanosekundy) nie będą dokładnie obliczały upływu czasu z powodu przepełnienia numerycznego.

Na przykład, aby zmierzyć, ile czasu zajmuje wykonanie kodu:

long startTime = System.nanoTime();    
// ... the code being measured ...    
long estimatedTime = System.nanoTime() - startTime;

Zobacz także: JavaDoc System.nanoTime () i JavaDoc System.currentTimeMillis (), aby uzyskać więcej informacji.

dancavallaro
źródło
20
Czy na pewno znasz różnicę między dokładnością a precyzją? Nie ma możliwości, aby był on dokładny z dokładnością do nanosekundy.
mmcdole
6
Przepraszam, miałem na myśli precyzyjne. Użyłem tego terminu luźno, ale zgadzam się, że był mylący (i niewłaściwe użycie tego słowa).
dancavallaro,
4
@dancavallaro, dzięki za informację. Jeśli nie masz nic przeciwko, zredagowałem twoją odpowiedź, aby dołączyć cytat z dokumentów i naprawiłem linki
mmcdole
135
Ta odpowiedź jest technicznie poprawna przy wyborze nanoTime (), ale całkowicie przeskakuje nad niezwykle ważną kwestią. nanoTime (), jak mówi doktor, jest precyzyjnym zegarem. currentTimeMillis () NIE JEST TIMEREM, to „zegar ścienny”. nanoTime () zawsze generuje dodatni upływ czasu, currentTimeMillis nie (np. jeśli zmienisz datę, uderzysz sekundę przestępną itp.) Jest to niezwykle ważne rozróżnienie dla niektórych rodzajów systemów.
charstar
11
Czas zmiany użytkownika i synchronizacja NTP są pewne, ale dlaczego miałoby się currentTimeMilliszmieniać z powodu DST? Przełącznik DST nie zmienia liczby sekund po epoce. Może to być „zegar ścienny”, ale jest on oparty na UTC. Na podstawie strefy czasowej i ustawień czasu letniego musisz określić, co przekłada się na twój czas lokalny (lub użyj innych narzędzi Java, aby to zrobić za Ciebie).
Shadow Man
100

Ponieważ nikt inny nie wspomniał o tym ...

Porównywanie wyników System.nanoTime()połączeń między różnymi wątkami nie jest bezpieczne . Nawet jeśli zdarzenia wątków przebiegają w przewidywalnej kolejności, różnica w nanosekundach może być dodatnia lub ujemna.

System.currentTimeMillis() jest bezpieczny w użyciu między wątkami.

pyzaty
źródło
4
W systemie Windows, który działa tylko do dodatku SP2, zgodnie z: stackoverflow.com/questions/510462/...
Peter Schmitz
3
Każdego dnia uczysz się czegoś nowego. Podejrzewam jednak, że biorąc pod uwagę, że w przeszłości było to niebezpieczne (zdecydowanie zwraca nonsensowne odczyty w wątkach), takie użycie jest prawdopodobnie nadal poza specyfikacją i dlatego prawdopodobnie należy go unikać.
pyzaty
4
@jgubby: Bardzo interesujące ... jakieś odniesienie do wsparcia, które nie jest bezpieczne, aby porównać wyniki wywołań System.nanoTime () między różnymi wątkami ? Warto zobaczyć następujące linki: bugs.sun.com/bugdatabase/view_bug.do?bug_id=6519418 docs.oracle.com/javase/7/docs/api/java/lang/…
user454322
15
Patrząc na opis wymieniony tutaj: docs.oracle.com/javase/7/docs/api/java/lang/… , wydaje się, że różnica między wartościami zwracanymi z nanoTime jest ważna do porównania, o ile pochodzą one z tego samego JVM -The values returned by this method become meaningful only when the difference between two such values, obtained within the same instance of a Java virtual machine, is computed.
Tuxdude,
7
JavaDoc dlananoTime() mówi: To samo pochodzenie jest używane przez wszystkie wywołania tej metody w wystąpieniu wirtualnej maszyny Java; inne instancje maszyn wirtualnych prawdopodobnie będą miały inne pochodzenie. co oznacza, że nie zwraca te same całej wątków.
Simon Forsberg
58

Aktualizacja autorstwa Arkadiy : Zauważyłem bardziej prawidłowe zachowanie System.currentTimeMillis()systemu Windows 7 w Oracle Java 8. Czas został zwrócony z dokładnością do 1 milisekundy. Kod źródłowy w OpenJDK nie zmienił się, więc nie wiem, co powoduje lepsze zachowanie.


David Holmes z firmy Sun opublikował kilka lat temu artykuł na blogu, który bardzo szczegółowo omawia interfejsy API synchronizacji Java (w szczególności System.currentTimeMillis()i System.nanoTime()), kiedy chcesz z nich korzystać i jak działają one wewnętrznie.

Wewnątrz maszyny Wirtualnej Hotspot: Zegary, zegary i planowanie zdarzeń - Część I - Windows

Jednym z bardzo interesujących aspektów timera używanego przez Javę w systemie Windows do interfejsów API z parametrem timed wait jest to, że rozdzielczość timera może się zmieniać w zależności od tego, jakie inne wywołania API mogły zostać wykonane - w całym systemie (nie tylko w konkretnym procesie) . Pokazuje przykład, w którym użycie Thread.sleep()spowoduje zmianę rozdzielczości.

Michael Burr
źródło
1
@Arkadiy: Czy masz źródło oświadczenia w aktualizacji?
Lii,
@Lii - Niestety nie. Chodzi mi uruchomiony kod w tej kwestii: stackoverflow.com/questions/34090914/... . Kod zapewnia precyzję 15ms przy Javie 7 i 1 ms precyzję przy Javie 8
2
Śledziłem currentTimeMillis do os :: javaTimeMillis w hotspocie / src / os / windows / vm / os_windows.cpp w OpenJDK ( hg.openjdk.java.net/jdk8/jdk8/hotspot/file/87ee5ee27509/src/os/... ) . Wygląda na to, że nadal jest to GetSystemTimeAsFileTime, więc nie wiem, skąd pochodzi zmiana. Lub jeśli to w ogóle ważne. Przetestuj przed użyciem.
zmiana zachowania jest wynikiem zmieniających się jak GetSystemTimeAsFileTimepracował w XP vs 7. Zobacz tutaj po więcej szczegółów (TL; dr zrobiło się bardziej precyzyjne, ponieważ cały system wprowadził pewne bardziej precyzyjnych metod pomiaru czasu).
11

System.nanoTime()nie jest obsługiwany w starszych JVM. Jeśli jest to problem, trzymaj sięcurrentTimeMillis

Jeśli chodzi o dokładność, masz prawie rację. Na NIEKTÓRYCH komputerach z systemem Windows currentTimeMillis()ma rozdzielczość około 10 ms (nie 50 ms). Nie jestem pewien, dlaczego, ale niektóre maszyny z systemem Windows są tak samo dokładne, jak maszyny z systemem Linux.

W przeszłości używałem GAGETimeru z umiarkowanym sukcesem.

Paul Morel
źródło
3
„starsze JVM” jak w jakich? Java 1.2 czy coś?
Simon Forsberg,
1
System.nanoTime () został wprowadzony w Javie 1.5 w 2004 roku. Java 1.4 rozszerzyła obsługę w 2013 roku, więc można śmiało powiedzieć, że można polegać na System.nanoTime (), a ta odpowiedź jest nieaktualna.
Dave L.
9

Jak powiedzieli inni, currentTimeMillis to czas zegarowy, który zmienia się z powodu czasu letniego, użytkowników zmieniających ustawienia czasu, sekund przestępnych i synchronizacji czasu internetowego. Jeśli Twoja aplikacja zależy od monotonicznie rosnących wartości upływającego czasu, możesz zamiast tego preferować nanoTime.

Możesz myśleć, że gracze nie będą majstrować przy ustawieniach czasu podczas gry, a może masz rację. Ale nie należy lekceważyć zakłóceń spowodowanych synchronizacją czasu w Internecie lub być może zdalnych użytkowników komputerów stacjonarnych. Interfejs API nanoTime jest odporny na tego rodzaju zakłócenia.

Jeśli chcesz użyć czasu zegara, ale uniknąć nieciągłości z powodu synchronizacji czasu w Internecie, możesz rozważyć klienta NTP, takiego jak Meinberg, który „dostosowuje” częstotliwość zegara, aby ją wyzerować, zamiast okresowego resetowania zegara.

Mówię z własnego doświadczenia. W opracowanej przeze mnie aplikacji pogodowej dostawałem przypadkowo gwałtowne skoki prędkości wiatru. Zajęło mi trochę czasu, zanim zdałem sobie sprawę, że moja podstawa czasu została zakłócona przez zachowanie czasu na typowym komputerze. Wszystkie moje problemy zniknęły, kiedy zacząłem używać nanoTime. Spójność (monotoniczność) była ważniejsza dla mojej aplikacji niż surowa precyzja lub absolutna dokładność.

KarlU
źródło
19
„currentTimeMillis to czas, który zmienia się z powodu czasu letniego” ... jestem całkiem pewien, że to stwierdzenie jest fałszywe. System.currentTimeMillis()zgłasza upływ czasu (w milisekundach) od Epoki Uniksa / Posixa, czyli o północy, 1 stycznia 1970 UTC. Ponieważ UTC nigdy nie jest korygowane o czas letni, ta wartość nie zostanie przesunięta, gdy lokalne strefy czasowe dodadzą lub poddadzą przesunięcie czasowi lokalnemu o czas letni. Co więcej, Java Time Scale wygładza sekundy przestępne, dzięki czemu dni wydają się mieć 86 400 sekund.
scottb
5

Tak, jeśli taka precyzja jest wymagana, użyj System.nanoTime(), ale pamiętaj, że potrzebujesz JVM Java 5+.

W moich systemach XP widzę czas systemowy zgłaszany do co najmniej 100 mikrosekund 278 nanosekund przy użyciu następującego kodu:

private void test() {
    System.out.println("currentTimeMillis: "+System.currentTimeMillis());
    System.out.println("nanoTime         : "+System.nanoTime());
    System.out.println();

    testNano(false);                                                            // to sync with currentTimeMillis() timer tick
    for(int xa=0; xa<10; xa++) {
        testNano(true);
        }
    }

private void testNano(boolean shw) {
    long strMS=System.currentTimeMillis();
    long strNS=System.nanoTime();
    long curMS;
    while((curMS=System.currentTimeMillis()) == strMS) {
        if(shw) { System.out.println("Nano: "+(System.nanoTime()-strNS)); }
        }
    if(shw) { System.out.println("Nano: "+(System.nanoTime()-strNS)+", Milli: "+(curMS-strMS)); }
    }
Lawrence Dol
źródło
4

Do grafiki gry i płynnych aktualizacji pozycji użyj System.nanoTime()raczej niż System.currentTimeMillis(). W grze przeszedłem z currentTimeMillis () na nanoTime () i uzyskałem znaczną poprawę płynności ruchu.

Choć jedna milisekunda może wydawać się, że powinna być już dokładna, wizualnie tak nie jest. Czynniki, które nanoTime()można poprawić, obejmują:

  • dokładne pozycjonowanie pikseli poniżej rozdzielczości zegara ściennego
  • możliwość wygładzania między pikselami, jeśli chcesz
  • Niedokładność zegara ściennego systemu Windows
  • jitter zegara (niespójność, kiedy zegar ścienny faktycznie tyka do przodu)

Jak sugerują inne odpowiedzi, nanoTime ma koszt wydajności, jeśli jest wywoływany wielokrotnie - najlepiej byłoby wywołać go tylko raz na ramkę i użyć tej samej wartości do obliczenia całej ramki.

Thomas W.
źródło
1

Mam dobre doświadczenie z nanotime . Zapewnia czas zegara ściennego jako dwie długości (sekundy od epoki i nanosekundy w tej sekundzie), przy użyciu biblioteki JNI. Jest dostępny ze wstępnie skompilowaną częścią JNI dla Windows i Linux.

Jon Bright
źródło
1

System.currentTimeMillis()nie jest bezpieczny dla upływu czasu, ponieważ ta metoda jest wrażliwa na zmiany systemu w czasie rzeczywistym. Powinieneś użyć System.nanoTime. Zapoznaj się z pomocą systemu Java:

O metodzie nanoTime:

.. Ta metoda zapewnia dokładność w nanosekundach, ale niekoniecznie w nanosekundach (czyli jak często zmienia się wartość) - nie udziela się żadnych gwarancji, z wyjątkiem tego, że rozdzielczość jest co najmniej tak dobra, jak w przypadku currentTimeMillis () ..

Jeśli wykorzystasz System.currentTimeMillis()czas, który upłynął, może być ujemny (wstecz <- do przyszłości)

Ricardo Gasca
źródło
-3

jedną rzeczą tutaj jest niespójność metody nanoTime. nie daje bardzo spójnych wartości dla tego samego wejścia. prąd bieżący Millis ma znacznie lepszą wydajność i spójność, a także, choć nie tak precyzyjny jak nanoTime, ma niższy margines błędu , a tym samym większą dokładność jego wartości. sugerowałbym zatem użycie currentTimeMillis

Sarvesh
źródło
6
Jak zauważono w innych odpowiedziach i komentarzach, currentTimeMillis podlega zmianom zegara systemowego i dlatego jest złym wyborem do obliczania upływu czasu od czasu wcześniejszego zdarzenia w JVM.
znaczniki pojedynków