System.nanoTime () é completamente inútil?

153

Conforme documentado na publicação do blog Cuidado com System.nanoTime () em Java , em sistemas x86, o System.nanoTime () do Java retorna o valor do tempo usando um contador específico da CPU . Agora considere o seguinte caso que eu uso para medir o tempo de uma chamada:

long time1= System.nanoTime();
foo();
long time2 = System.nanoTime();
long timeSpent = time2-time1;

Agora, em um sistema com vários núcleos, pode ser que, após medir o tempo1, o encadeamento seja agendado para um processador diferente cujo contador seja menor que o da CPU anterior. Assim, poderíamos obter um valor no tempo2 que é menor que o tempo1. Assim, obteríamos um valor negativo em timeSpent.

Considerando esse caso, não é o System.nanotime que é praticamente inútil por enquanto?

Eu sei que mudar a hora do sistema não afeta o nanotime. Esse não é o problema que descrevi acima. O problema é que cada CPU mantém um contador diferente desde que foi ligado. Esse contador pode ser menor na segunda CPU em comparação com a primeira CPU. Como o encadeamento pode ser agendado pelo sistema operacional para a segunda CPU após obter o time1, o valor de timeSpent pode estar incorreto e até negativo.

pdeva
fonte
2
Não tenho uma resposta, mas concordo com você. Talvez deva ser considerado um bug na JVM.
Aaron Digulla
2
essa publicação está incorreta e o uso do TSC é lento, mas você precisa conviver com: bugs.sun.com/bugdatabase/view_bug.do?bug_id=6440250 O TSC também pode ser útil por meio do hypervisor, mas fica lento novamente.
bestsss
1
E, é claro, você pode executar em uma máquina virtual onde uma CPU pode aparecer no meio de uma sessão: D
Expiação Limitada

Respostas:

206

Esta resposta foi escrita em 2011 do ponto de vista do que o Sun JDK da época em execução nos sistemas operacionais da época realmente fazia. Isso foi há muito tempo atrás! A resposta de leventov oferece uma perspectiva mais atualizada.

Esse post está errado e nanoTimeé seguro. Há um comentário na postagem com um link de David Holmes , um cara em tempo real e simultâneo da Sun. Diz:

System.nanoTime () é implementado usando a API QueryPerformanceCounter / QueryPerformanceFrequency [...] O mecanismo padrão usado pelo QPC é determinado pela camada de abstração de hardware (HAL) [...] Esse padrão muda não apenas no hardware, mas também no SO versões. Por exemplo, o Windows XP Service Pack 2 mudou as coisas para usar o timer de gerenciamento de energia (PMTimer) em vez do contador de carimbo de data / hora do processador (TSC) devido a problemas com o TSC não serem sincronizados em diferentes processadores nos sistemas SMP e devido ao fato de sua frequência pode variar (e, portanto, sua relação com o tempo decorrido) com base nas configurações de gerenciamento de energia.

Portanto, no Windows, esse era um problema até o WinXP SP2, mas não é agora.

Não consigo encontrar uma parte II (ou mais) que fala sobre outras plataformas, mas esse artigo inclui uma observação de que o Linux encontrou e resolveu o mesmo problema da mesma maneira, com um link para a FAQ do clock_gettime (CLOCK_REALTIME) , que diz:

  1. O clock_gettime (CLOCK_REALTIME) é consistente em todos os processadores / núcleos? (O arco importa? Por exemplo, ppc, arm, x86, amd64, sparc).

ele deve ou é considerado buggy.

No entanto, em x86 / x86_64, é possível ver TSCs de frequência variável ou não sincronizada causar inconsistências de tempo. Os kernels 2.4 realmente não tinham proteção contra isso, e os kernels 2.6 anteriores também não se saíam muito bem aqui. A partir da versão 2.6.18, a lógica para detectar isso é melhor e, geralmente, voltaremos a uma fonte de relógio segura.

O ppc sempre tem uma base de tempo sincronizada, portanto isso não deve ser um problema.

Portanto, se o link de Holmes puder ser lido como implicando que nanoTimechamadasclock_gettime(CLOCK_REALTIME) , é seguro a partir do kernel 2.6.18 no x86 e sempre no PowerPC (porque a IBM e a Motorola, ao contrário da Intel, sabem como projetar microprocessadores).

Infelizmente, não há menção a SPARC ou Solaris. E, é claro, não temos idéia do que as JVMs da IBM fazem. Mas as JVMs da Sun no Windows e Linux modernos acertam isso.

EDIT: Esta resposta é baseada nas fontes que cita. Mas ainda me preocupo que isso possa estar completamente errado. Algumas informações mais atualizadas seriam realmente valiosas. Acabei de encontrar um link para um artigo mais recente de quatro anos sobre os relógios do Linux que poderia ser útil.

Tom Anderson
fonte
13
Até o WinXP SP2 parece sofrer. Executando o exemplo de código original, com void foo() { Thread.sleep(40); }eu tenho um tempo negativo (-380 ms!) Usando um único Athlon 64 X2 4200+processador
Luke Usherwood
Suponho que não haja atualizações sobre isso, wrt. comportamento no Linux, BSD ou outras plataformas?
precisa
6
Boa resposta, deve adicionar um link para a exploração mais recente deste tópico: shipilev.net/blog/2014/nanotrusting-nanotime
Nitsan Wakart 10/15/15
1
@ SOFe: Oh, isso é uma pena. Está no arquivo da web , felizmente. Vou ver se consigo rastrear uma versão atual.
Tom Anderson
1
Nota: O OpenJDK não manteve as especificações até o OpenJDK 8u192, consulte bugs.openjdk.java.net/browse/JDK-8184271 . Certifique-se de usar pelo menos a versão mais recente do OpenJDK 8 ou OpenJDK 11+.
leventov 04/02/19
35

Pesquisei um pouco e descobri que, se alguém está sendo pedante, sim, pode ser considerado inútil ... em situações particulares ... depende de quão sensível ao tempo são suas exigências ...

Confira esta citação no site Java Sun:

O relógio em tempo real e System.nanoTime () são baseados na mesma chamada do sistema e, portanto, no mesmo relógio.

Com o Java RTS, todas as APIs baseadas em tempo (por exemplo, Temporizadores, Encadeamentos Periódicos, Monitoramento de Prazos etc.) são baseadas no cronômetro de alta resolução. E, junto com as prioridades em tempo real, eles podem garantir que o código apropriado seja executado no momento certo para restrições em tempo real. Por outro lado, as APIs Java SE comuns oferecem apenas alguns métodos capazes de lidar com tempos de alta resolução, sem garantia de execução em um determinado momento. O uso de System.nanoTime () entre vários pontos no código para realizar medições de tempo decorrido deve sempre ser preciso.

Java também tem uma ressalva para o método nanoTime () :

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 fixo, mas arbitrário (talvez no futuro, portanto, os valores podem ser negativos). Esse 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,3 anos (2 63 nanossegundos) não calcularão com precisão o tempo decorrido devido ao excesso numérico.

Parece que a única conclusão que pode ser tirada é que nanoTime () não pode ser considerado um valor preciso. Dessa forma, se você não precisar medir tempos com apenas nano segundos de distância, esse método será bom o suficiente, mesmo que o valor retornado resultante seja negativo. No entanto, se você estiver precisando de maior precisão, eles parecerão recomendar o uso do JAVA RTS.

Portanto, para responder à sua pergunta ... o nanoTime () não é inútil ... não é o método mais prudente a ser usado em todas as situações.

mezoid
fonte
3
> esse método é bom o suficiente, mesmo que o valor retornado resultante seja negativo. Eu não entendo isso, se o valor no tempo gasto for negativo, então como é útil para medir o tempo gasto em foo ()?
pdeva
3
tudo bem, porque você só está preocupado com o valor absoluto da diferença. ou seja, se a sua medição é o tempo t em que t = t2 - t1, você quer saber | t | .... e daí se o valor for negativo ... mesmo com o problema de vários núcleos, o impacto raramente será de poucos nanossegundos de qualquer maneira.
mezoid
3
Para fazer backup do @Aaron: t2 e t1 podem ser negativos, mas (t2-t1) não deve ser negativo.
jfs
2
Aaron: isso é exatamente o que quero dizer. t2-t1 nunca deve ser negativo, caso contrário, temos um bug.
Pdeva #
12
@ pdeva - mas você está entendendo mal o que o documento diz. Você está levantando um problema não. Há algum momento considerado "0". Os valores retornados por nanoTime () são precisos em relação a esse tempo. É uma linha do tempo monotonicamente crescente. Você pode estar recebendo uma série de números da parte negativa dessa linha do tempo. -100, -99, -98(Valores obviamente muito maiores na prática). Eles estão indo na direção correta (aumentando), então não há problema aqui.
Página
18

Não há necessidade de debater, basta usar a fonte. Aqui, o SE 6 para Linux, tire suas próprias conclusões:

jlong os::javaTimeMillis() {
  timeval time;
  int status = gettimeofday(&time, NULL);
  assert(status != -1, "linux error");
  return jlong(time.tv_sec) * 1000  +  jlong(time.tv_usec / 1000);
}


jlong os::javaTimeNanos() {
  if (Linux::supports_monotonic_clock()) {
    struct timespec tp;
    int status = Linux::clock_gettime(CLOCK_MONOTONIC, &tp);
    assert(status == 0, "gettime error");
    jlong result = jlong(tp.tv_sec) * (1000 * 1000 * 1000) + jlong(tp.tv_nsec);
    return result;
  } else {
    timeval time;
    int status = gettimeofday(&time, NULL);
    assert(status != -1, "linux error");
    jlong usecs = jlong(time.tv_sec) * (1000 * 1000) + jlong(time.tv_usec);
    return 1000 * usecs;
  }
}
blais
fonte
7
Isso é útil apenas se você souber o que a API usada faz. A API usada é implementada pelo sistema operacional; este código está correto a especificação da API usada (clock_gettime / gettimeofday), mas como outros apontaram, alguns sistemas operacionais não atualizados têm implementações de buggy.
Bluesorblade 30/04
18

Desde o Java 7, System.nanoTime()é garantido que seja seguro pela especificação JDK. System.nanoTime()O Javadoc deixa claro que todas as invocações observadas em uma JVM (ou seja, em todos os threads) são monotônicas:

O valor retornado representa nanossegundos desde algum tempo de origem fixo, mas arbitrário (talvez no futuro, portanto, os valores podem ser negativos). 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.

A implementação da JVM / JDK é responsável por resolver as inconsistências que podem ser observadas quando os utilitários do SO subjacente são chamados (por exemplo, os mencionados na resposta de Tom Anderson ).

A maioria das outras respostas antigas para essa pergunta (escritas em 2009–2012) expressa FUD que provavelmente era relevante para Java 5 ou Java 6, mas não é mais relevante para versões modernas de Java.

Vale ressaltar, no entanto, que, apesar da segurança nanoTime()das garantias do JDK , houve vários bugs no OpenJDK, impedindo essa garantia em determinadas plataformas ou sob certas circunstâncias (por exemplo, JDK-8040140 , JDK-8184271 ). Não há bugs abertos (conhecidos) no OpenJDK wrtnanoTime() , mas a descoberta de um novo bug ou uma regressão em uma versão mais recente do OpenJDK não deve chocar ninguém.

Com isso em mente, o código usado nanoTime()para bloqueio cronometrado, espera em intervalos, tempos limite, etc. deve preferencialmente tratar diferenças de tempo negativas (tempos limite) como zeros, em vez de lançar exceções. Esta prática é também preferível porque é consistente com o comportamento de todos os métodos de espera temporizada em todas as classes em java.util.concurrent.*, por exemplo Semaphore.tryAcquire(), Lock.tryLock(), BlockingQueue.poll(), etc.

No entanto, nanoTime()ainda deve ser preferido para a implementação de bloqueio cronometrado, intervalo de espera, tempo limite, etc., currentTimeMillis()porque este último está sujeito ao fenômeno "tempo atrasado" (por exemplo, devido à correção da hora do servidor), ou currentTimeMillis()seja, não é adequado para medir intervalos de tempo em absoluto. Veja esta resposta para mais informações.

Em vez de usar nanoTime()diretamente para medições de tempo de execução de código, estruturas e perfis de benchmarking especializados devem ser usados ​​preferencialmente, por exemplo, JMH e async-profiler no modo de criação de perfil de relógio de parede .

leventov
fonte
6

O Linux corrige discrepâncias entre CPUs, mas o Windows não. Eu sugiro que você suponha que System.nanoTime () é preciso apenas para cerca de 1 micro-segundo. Uma maneira simples de obter um tempo mais longo é chamar foo () 1000 ou mais vezes e dividir o tempo por 1000.

Peter Lawrey
fonte
2
Você poderia fornecer uma referência (comportamento no Linux e Windows)?
jfs
Infelizmente, o método proposto geralmente é altamente impreciso, pois cada evento que cai no slot de atualização de relógio de parede de +/- 100ms geralmente retorna zero para operações de sub-segundo. A soma de 9 operações, cada uma com duração de zero é, bem, zero, dividida por nove é ... zero. Por outro lado, o uso de System.nanoTime () fornecerá durações de eventos relativamente precisas (diferentes de zero), que depois somadas e divididas pelo número de eventos fornecerão uma média altamente precisa.
Darrell Teague
O @DarrellTeague somar 1000 eventos e adicioná-los é o mesmo que o horário de ponta a ponta.
precisa saber é o seguinte
O @DarrellTeague System.nanoTime () tem precisão de 1 microssegundo ou melhor (não 100.000 microssegundos) na maioria dos sistemas. A média de muitas operações é relevante apenas quando você reduz alguns microssegundos e apenas em determinados sistemas.
precisa saber é o seguinte
1
Desculpas, pois houve alguma confusão sobre o idioma usado na "soma" de eventos. Sim, se o tempo estiver marcado no início das operações de, digamos, 1000 segundos, eles serão executados e o tempo será marcado novamente no final e dividido - isso funcionaria para algum sistema em desenvolvimento para obter uma boa aproximação da duração de um determinado evento.
Darrell Teague
5

Absolutamente não é inútil. Os aficionados de tempo apontam corretamente o problema de vários núcleos, mas em aplicativos de palavras reais ele costuma ser radicalmente melhor que o currentTimeMillis ().

Ao calcular as posições gráficas no quadro, o nanoTime () leva a MUITO movimento mais suave no meu programa.

E eu só testei em máquinas com vários núcleos.

Yuvi Masory
fonte
5

Vi um tempo decorrido negativo relatado usando System.nanoTime (). Para ser claro, o código em questão é:

    long startNanos = System.nanoTime();

    Object returnValue = joinPoint.proceed();

    long elapsedNanos = System.nanoTime() - startNanos;

e a variável 'elapsedNanos' teve um valor negativo. (Tenho certeza de que a chamada intermediária também levou menos de 293 anos, que é o ponto de sobrecarga para nanos armazenados em longos :)

Isso ocorreu usando um IBM v1.5 JRE 64bit no hardware IBM P690 (multi-core) executando o AIX. Eu só vi esse erro ocorrer uma vez, então parece extremamente raro. Não sei a causa - é um problema específico de hardware, um defeito da JVM - não sei. Também não sei as implicações para a precisão do nanoTime () em geral.

Para responder à pergunta original, não acho que o nanoTime seja inútil - ele fornece um tempo abaixo de milissegundos, mas há um risco real (não apenas teórico) de ser impreciso, o que você precisa levar em consideração.

Basil Vandegriend
fonte
Infelizmente, parece haver algum problema de SO / hardware. A documentação afirma que os valores principais podem ser negativos, mas (maior negativo menos menor) ainda deve ser um valor positivo. De fato, a suposição é de que, no mesmo encadeamento, a chamada nanoTime () sempre deve retornar um valor positivo ou negativo. Nunca vi isso em vários sistemas Unix e Windows há muitos anos, mas parece possível, especialmente se o hardware / OS estiver dividindo essa operação aparentemente atômica entre os processadores.
Darrell Teague
@BasilVandegriend não é um bug em lugar nenhum. De acordo com a documentação, raramente o segundo System.nanoTime () no seu exemplo pode ser executado em uma CPU diferente e os valores de nanoTime calculados nessa CPU podem ser inferiores aos valores calculados na primeira CPU. Então, um valor -ve para elapsedNanos é possível
interminável
2

Isso não parece ser um problema em um Core 2 Duo executando o Windows XP e o JRE 1.5.0_06.

Em um teste com três threads, não vejo System.nanoTime () retrocedendo. Os processadores estão ocupados e os threads entram em suspensão ocasionalmente para provocar a movimentação de threads.

[EDIT] Eu acho que isso acontece apenas em processadores fisicamente separados, ou seja, que os contadores são sincronizados para vários núcleos no mesmo dado.

starblue
fonte
2
Provavelmente isso não acontece o tempo todo, mas devido à maneira como o nanotime () é implementado, a possibilidade está sempre lá.
pdeva
Eu acho que isso só acontece em processadores fisicamente separados, ou seja, que os contadores são sincronizados para vários núcleos no mesmo dado.
starblue
Mesmo isso depende da implementação específica, IIRC. Mas isso é algo que o sistema operacional deve cuidar.
Bluesorblade 30/04
1
Os contadores RDTSC em vários núcleos do mesmo processador x86 não são necessariamente sincronizados - alguns sistemas modernos permitem que diferentes núcleos sejam executados em velocidades diferentes.
Jules
2

Não, não é ... Depende apenas da sua CPU, verifique o temporizador de eventos de alta precisão para saber como / por que as coisas são tratadas de maneira diferente de acordo com a CPU.

Basicamente, leia a fonte do seu Java e verifique o que sua versão faz com a função, e se ela funcionar na CPU, você a executará.

A IBM ainda sugere que você o use para comparações de desempenho (uma publicação de 2008, mas atualizada).

Ric Tokyo
fonte
Como toda implementação definiu o comportamento, "ressalva emptor!"
David Schmitt
2

Estou ligando ao que é essencialmente a mesma discussão em que Peter Lawrey está fornecendo uma boa resposta. Por que recebo um tempo decorrido negativo usando System.nanoTime ()?

Muitas pessoas mencionaram que no Java System.nanoTime () poderia retornar um tempo negativo. Peço desculpas por repetir o que outras pessoas já disseram.

  1. nanoTime () não é um relógio, mas um contador de ciclos da CPU.
  2. O valor de retorno é dividido pela frequência para se parecer com o tempo.
  3. A frequência da CPU pode variar.
  4. Quando seu encadeamento é agendado em outra CPU, existe a chance de obter nanoTime (), o que resulta em uma diferença negativa. Isso é lógico. Contadores entre CPUs não são sincronizados.
  5. Em muitos casos, você pode obter resultados bastante enganadores, mas não seria capaz de saber porque o delta não é negativo. Pense nisso.
  6. (não confirmado) Acho que você pode obter um resultado negativo, mesmo na mesma CPU, se as instruções forem reordenadas. Para evitar isso, você teria que invocar uma barreira de memória serializando suas instruções.

Seria legal se System.nanoTime () retornasse coreID onde foi executado.

Vórtice
fonte
1
Todos os pontos, exceto 3. e 5., estão errados. 1. nanoTime () não é um contador de ciclo da CPU, é tempo nano . 2. Como o valor do nanoTime é produzido é específico da plataforma. 4. Não, a diferença não pode ser negativa, de acordo com a especificação nanoTime (). Supondo que o OpenJDK não tenha erros wrt nanoTime (), e não há erros não resolvidos conhecidos no momento. 6. As chamadas nanoTime não podem ser reordenadas em um encadeamento porque é um método nativo e a JVM respeita a ordem do programa. A JVM nunca reordena as chamadas de método nativo porque não sabe o que acontece dentro delas e, portanto, não pode provar que essas reordenações seriam seguras.
leventov 04/02/19
No que diz respeito à 5. nanoTime (), os resultados das diferenças podem ser enganosos, mas não pelos motivos apresentados em outros pontos deste manual. Porém, pelas razões apresentadas aqui: shipilev.net/blog/2014/nanotrusting-nanotime
leventov 04/02/19
Ironicamente, em relação ao 6. houve um bug no OpenJDK especificamente devido a novos pedidos : bugs.openjdk.java.net/browse/JDK-8184271 . No OpenJDK, nanoTime () é um intrínseco e foi permitido reordenar, o que foi um bug.
leventov 04/02/19
@ levev, então nanotime () é seguro de usar? ou seja, não pode retornar valores negativos e é ~ preciso na medida em que o tempo passa. Não vejo o objetivo de expor uma função da API repleta de problemas. Esta publicação é uma prova, iniciada em 2009 e ainda comentada em 2019. Para assuntos críticos, imagino que as pessoas confiem em cartões de ponto como Symmetricom
Vortex
1
O seu número 4 diz sobre diferença negativa , não valores: "há uma chance de obter nanoTime () que resulta em uma diferença negativa."
leventov 5/02/19
1

Java é multiplataforma e nanoTime depende da plataforma. Se você usa Java - quando não usa o nanoTime. Encontrei erros reais em diferentes implementações de jvm com esta função.

knork kilork
fonte
0

A documentação do Java 5 também recomenda o uso desse método para a mesma finalidade.

Esse 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.

Doc da API do Java 5


fonte
0

Além disso, System.currentTimeMillies()muda quando você altera o relógio do sistema, enquanto System.nanoTime()não muda , portanto, o último é mais seguro para medir durações.

RobAu
fonte
-3

nanoTimeé extremamente inseguro para o tempo. Eu tentei nos meus algoritmos básicos de teste de primalidade e deu respostas que estavam literalmente com um segundo de diferença para a mesma entrada. Não use esse método ridículo. Preciso de algo que seja mais preciso e preciso do que ganhar tempo, mas não tão ruim quanto nanoTime.

sarvesh
fonte
sem uma fonte ou uma explicação melhor este comentário é inútil
IC3