System.currentTimeMillis vs System.nanoTime

379

Precisão vs. Precisão

O que eu gostaria de saber é se devo usar System.currentTimeMillis () ou System.nanoTime () ao atualizar as posições do meu objeto no meu jogo? A mudança de movimento deles é diretamente proporcional ao tempo decorrido desde a última ligação e eu quero ser o mais preciso possível.

Eu li que existem alguns problemas sérios de resolução de tempo entre diferentes sistemas operacionais (ou seja, que o Mac / Linux tem uma resolução de quase 1 ms, enquanto o Windows tem uma resolução de 50ms?). Estou executando meus aplicativos principalmente no Windows e a resolução de 50ms parece bastante imprecisa.

Existem opções melhores que as duas que eu listei?

Alguma sugestão / comentário?

mmcdole
fonte
81
nanoTimegeralmente é mais preciso que o currentTimeMillis, mas também é uma ligação relativamente cara. currentTimeMillis()executado em alguns (5-6) relógios da CPU, o nanoTime depende da arquitetura subjacente e pode ter mais de 100 relógios da CPU.
bestsss 21/02
10
Vocês todos sabem que o Windows geralmente tem uma granularidade de 1000ms / 64, certo? Que são 15,625 ms ou 15625000 nanossegundos!
7
Eu não acho que cem ciclos extras de relógio afetarão seu jogo, e o trade-off provavelmente valeria a pena. Você só deve chamar o método uma vez por atualização de jogo e salvar o valor em mem, para que não adicione muita sobrecarga. Quanto à granularidade de diferentes plataformas, não faço ideia.
aglassman
9
O Windows tem uma granularidade de divisão de tempo DEFAULT de 1000ms / 64. Você pode aumentar isso por meio da API timeBeginPeriod nativa. PCs modernos também possuem cronômetros de alta resolução, além do cronômetro básico. Temporizadores de alta resolução são acessíveis através da chamada QueryPerformanceCounter.
Robin Davies
2
@Gohan - Este artigo entra em detalhes sobre os funcionamentos internos System.currentTimeMillis(): pzemtsov.github.io/2017/07/23/the-slow-currenttimemillis.html
Átila Tanyi

Respostas:

320

Se você está apenas procurando medições extremamente precisas do tempo decorrido , use System.nanoTime(). System.currentTimeMillis()fornecerá o tempo decorrido possível mais preciso possível em milissegundos desde a época, mas fornecerá System.nanoTime()um tempo nanosegundo preciso, em relação a algum ponto arbitrário.

Na documentação Java:

public static long nanoTime()

Retorna o valor atual do timer de sistema disponível mais preciso, em nanossegundos.

Este método pode ser usado apenas para medir o tempo decorrido e não está relacionado a nenhuma outra noção de sistema ou hora do relógio de parede. O valor retornado representa nanossegundos desde algum tempo de origem fixo, mas arbitrário (talvez no futuro, portanto, os valores podem ser negativos). Este método fornece precisão em nanossegundos, mas não necessariamente precisão em nanossegundos. Não há garantias sobre a frequência com que os valores mudam. As diferenças nas chamadas sucessivas que ultrapassam aproximadamente 292 anos (2 63 nanossegundos) não calcularão com precisão o tempo decorrido devido ao excesso numérico.

Por exemplo, para medir quanto tempo um código leva para executar:

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

Consulte também: JavaDoc System.nanoTime () e JavaDoc System.currentTimeMillis () para obter mais informações.

dancavallaro
fonte
20
Tem certeza de que sabe a diferença entre precisão e precisão? Não há como ser preciso com uma precisão de nanossegundos.
mmcdole 9/12/08
6
Desculpe, eu quis dizer preciso. Eu estava usando o termo livremente, mas concordo que era confuso (e um uso inadequado da palavra).
dancavallaro
4
@dancavallaro, obrigado pela informação. Se você não se importa, eu editei sua resposta para incluir uma citação dos docs e fixa-se a ligações
mmcdole
135
Esta resposta é tecnicamente correta na escolha de nanoTime (), mas encobre completamente um ponto extremamente importante. nanoTime (), como o documento diz, é um cronômetro de precisão. currentTimeMillis () NÃO É UM TEMPORIZADOR, é o "relógio de parede". O nanoTime () sempre produzirá um tempo decorrido positivo, o currentTimeMillis não (por exemplo, se você alterar a data, pressionar um segundo, etc.) Essa é uma distinção extremamente importante para alguns tipos de sistemas.
charstar
11
Hora de mudança do usuário e sincronização NTP, com certeza, mas por que currentTimeMillismudar devido ao horário de verão? A opção DST não altera o número de segundos após a época. Pode ser um "relógio de parede", mas é baseado no UTC. Você deve determinar com base nas configurações de fuso horário e de horário de verão para o que isso se traduz no horário local (ou usar outros utilitários Java para fazer isso por você).
Shadow Man
100

Desde que mais ninguém mencionou isso ...

Não é seguro comparar os resultados de System.nanoTime()chamadas entre diferentes segmentos. Mesmo que os eventos dos encadeamentos ocorram em uma ordem previsível, a diferença em nanossegundos pode ser positiva ou negativa.

System.currentTimeMillis() é seguro para uso entre threads.

gordinho
fonte
4
No Windows que só detém até SP2 de acordo com: stackoverflow.com/questions/510462/...
Peter Schmitz
3
Bem, você aprende algo novo todos os dias. Eu suspeito, no entanto, que, dado que não era seguro no passado (definitivamente retorna leituras sem sentido entre os threads), esse uso provavelmente ainda está fora das especificações e, portanto, provavelmente deve ser evitado.
Gubby
4
@jgubby: Muito interessante ... alguma referência ao suporte que não seja segura para comparar os resultados de chamadas System.nanoTime () entre diferentes segmentos ? Os links a seguir valem para ver: bugs.sun.com/bugdatabase/view_bug.do?bug_id=6519418 docs.oracle.com/javase/7/docs/api/java/lang/...
user454322
15
Observando a descrição mencionada aqui: docs.oracle.com/javase/7/docs/api/java/lang/… , parece que a diferença entre os valores retornados do nanoTime é válida para comparação, desde que sejam da mesma 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
O JavaDoc parananoTime() diz: A mesma origem é usada por todas as chamadas deste método em uma instância de uma máquina virtual Java; outras instâncias de máquina virtual provavelmente usarão uma origem diferente. o que significa que não retornam os mesmos em threads.
Simon Forsberg
58

Atualização por Arkadiy : observei um comportamento mais correto System.currentTimeMillis()no Windows 7 no Oracle Java 8. O tempo foi retornado com precisão de 1 milissegundo. O código-fonte do OpenJDK não mudou, então não sei o que causa o melhor comportamento.


David Holmes, da Sun, publicou um artigo no blog há alguns anos, que tem uma visão muito detalhada das APIs de tempo de Java (em particular System.currentTimeMillis()e System.nanoTime()), quando você deseja usar quais e como elas funcionam internamente.

Dentro da VM do ponto de acesso: relógios, cronômetros e eventos de agendamento - Parte I - Windows

Um aspecto muito interessante do timer usado pelo Java no Windows para APIs que possuem um parâmetro de espera temporizada é que a resolução do timer pode mudar dependendo de outras chamadas de API que tenham sido feitas - em todo o sistema (não apenas no processo específico) . Ele mostra um exemplo em que o uso Thread.sleep()causará essa alteração na resolução.

Michael Burr
fonte
11
@Arkadiy: Você tem uma fonte para a declaração na atualização?
Lii
@Lii - Infelizmente não. Vem de mim executando o código nesta pergunta: stackoverflow.com/questions/34090914/… . O código produz 15ms de precisão com Java 7 e 1 ms de precisão com Java 8
2
Rastreei o currentTimeMillis para os :: javaTimeMillis no hotspot / src / os / windows / vm / os_windows.cpp no ​​OpenJDK ( hg.openjdk.java.net/jdk8/jdk8/hotspot/file/87ee5ee27509/src/os/… ) . Parece que ainda é GetSystemTimeAsFileTime, então não sei de onde vem a mudança. Ou se é mesmo válido. Teste antes de usar.
a mudança de comportamento é o resultado de mudar a forma como GetSystemTimeAsFileTimefuncionou no XP vs 7. Veja aqui para mais detalhes (tl; dr ficou mais preciso, pois todo o sistema introduziu alguns métodos de tempo mais precisos).
11

System.nanoTime()não é suportado em JVMs mais antigas. Se isso é uma preocupação, fique comcurrentTimeMillis

Em relação à precisão, você está quase correto. Em algumas máquinas Windows, currentTimeMillis()tem uma resolução de cerca de 10ms (não 50ms). Não sei por que, mas algumas máquinas Windows são tão precisas quanto as máquinas Linux.

Eu usei GAGETimer no passado com sucesso moderado.

Paul Morel
fonte
3
"JVMs mais antigas" como em quais? Java 1.2 ou algo assim?
Simon Forsberg
11
O System.nanoTime () foi introduzido no Java 1.5 em 2004. O Java 1.4 estendeu o suporte em 2013, por isso é bastante seguro dizer que o System.nanoTime () agora pode ser usado e que essa resposta está desatualizada.
Dave L.
9

Como já foi dito, currentTimeMillis é a hora do relógio, que muda devido ao horário de verão, usuários alterando as configurações de hora, segundos bissextos e sincronização da hora da Internet. Se o seu aplicativo depende do aumento monotônico dos valores de tempo decorrido, você pode preferir o nanoTime.

Você pode pensar que os jogadores não vão mexer com as configurações de tempo durante o jogo, e talvez você esteja certo. Mas não subestime a interrupção devido à sincronização da hora da Internet ou talvez a usuários de computadores remotos. A API nanoTime é imune a esse tipo de interrupção.

Se você quiser usar a hora do relógio, mas evitar descontinuidades devido à sincronização da hora da Internet, considere um cliente NTP como o Meinberg, que "ajusta" a taxa do relógio para zerá-lo, em vez de apenas redefinir o relógio periodicamente.

Eu falo por experiência própria. Em um aplicativo climático que desenvolvi, eu estava obtendo picos de velocidade do vento que ocorriam aleatoriamente. Demorou um pouco para eu perceber que minha base de tempo estava sendo interrompida pelo comportamento da hora do relógio em um PC típico. Todos os meus problemas desapareceram quando comecei a usar o nanoTime. A consistência (monotonicidade) foi mais importante para minha aplicação do que a precisão bruta ou a precisão absoluta.

KarlU
fonte
19
"currentTimeMillis é a hora do relógio, que muda devido ao horário de verão" ... quase certeza de que esta afirmação é falsa. System.currentTimeMillis()relata o tempo decorrido (em milissegundos) desde a época Unix / Posix, que é Midnight, 1 de janeiro de 1970 UTC. Como o UTC nunca é ajustado para o horário de verão, esse valor não será compensado quando os fusos horários locais adicionarem ou sujeitarem deslocamentos aos horários locais para o horário de verão. Além disso, o Java Time Scale suaviza os segundos bissextos, para que os dias pareçam ter 86.400 segundos.
Scottb 23/05
5

Sim, se essa precisão for necessária System.nanoTime(), use , mas lembre-se de que você está exigindo uma JVM Java 5+.

Nos meus sistemas XP, vejo a hora do sistema relatada em pelo menos 100 microssegundos 278 nanossegundos usando o seguinte código:

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
fonte
4

Para gráficos de jogos e atualizações de posição suaves, use em System.nanoTime()vez de System.currentTimeMillis(). Eu mudei de currentTimeMillis () para nanoTime () em um jogo e obtive uma grande melhoria visual na suavidade do movimento.

Embora um milissegundo possa parecer que já deveria ser preciso, visualmente não é. Os fatores que nanoTime()podem melhorar incluem:

  • posicionamento preciso de pixels abaixo da resolução do relógio de parede
  • capacidade de anti-alias entre pixels, se você quiser
  • Imprecisão do relógio de parede do Windows
  • tremulação do relógio (inconsistência de quando o relógio de parede realmente avança)

Como outras respostas sugerem, o nanoTime tem um custo de desempenho se chamado repetidamente - seria melhor chamá-lo apenas uma vez por quadro e usar o mesmo valor para calcular o quadro inteiro.

Thomas W
fonte
1

Eu tive uma boa experiência com nanotime . Ele fornece o tempo do relógio de parede como dois longos (segundos desde a época e nanossegundos nesse segundo), usando uma biblioteca JNI. Está disponível com a parte JNI pré-compilada para Windows e Linux.

Jon Bright
fonte
1

System.currentTimeMillis()não é seguro por tempo decorrido, porque esse método é sensível às alterações do relógio em tempo real do sistema. Você deveria usar System.nanoTime. Consulte a ajuda do sistema Java:

Sobre o método nanoTime:

.. Este método fornece precisão em nanossegundos, mas não necessariamente resolução em nanossegundos (ou seja, com que frequência o valor é alterado) - nenhuma garantia é feita, exceto que a resolução é pelo menos tão boa quanto a de currentTimeMillis () ..

Se você usar System.currentTimeMillis()o tempo decorrido, pode ser negativo (Voltar <- para o futuro)

Ricardo Gasca
fonte
-3

uma coisa aqui é a inconsistência do método nanoTime. ele não fornece valores muito consistentes para a mesma entrada. e, portanto, mais precisão em seu valor. portanto, sugiro que você use currentTimeMillis

sarvesh
fonte
6
Conforme observado em outras respostas e comentários, currentTimeMillis está sujeito a alterações no relógio do sistema e, portanto, uma má opção para calcular o tempo decorrido desde algum evento anterior na JVM.
marcadores duelin