Java: quanto tempo usa um loop vazio?

Eu estou tentando testar a velocidade de autoboxing e unboxing em Java, mas quando eu tento compará-lo com um loop vazio em um primitivo, notei uma coisa curiosa. Este trecho:

for (int j = 0; j < 10; j++) { long t = System.currentTimeMillis(); for (int i = 0; i < 10000000; i++) ; t = System.currentTimeMillis() - t; System.out.print(t + " "); } 

Toda vez que eu executo isso, ele retorna o mesmo resultado:

6 7 0 0 0 0 0 0 0 0

Por que os dois primeiros loops sempre levam algum tempo, então o resto parece ser ignorado pelo sistema?

Nesta resposta a este post, diz-se que a compilation Just-In-Time poderá otimizar isso. Mas se sim, porque os primeiros dois loops ainda demoraram algum tempo?

O JIT é acionado APÓS um certo trecho de código ter sido executado muitas vezes.

O HotSpot JVM tentará identificar “pontos de access” em seu código. Hot spots são partes do seu código que são executadas muitas e muitas vezes. Para fazer isso, a JVM “contará” as execuções de várias instruções e, quando determinar que determinada peça é executada com frequência, ela acionará o JIT. (isso é uma aproximação, mas é fácil de entender, explicado dessa maneira).

O JIT (Just-In-Time) pega esse pedaço de código e tenta torná-lo mais rápido.

As técnicas usadas pelo JIT para fazer seu código rodar mais rápido são muito, mas o que mais comumente cria confusão são:

  1. Ele tentará determinar se essa parte do código usa variables ​​que não são usadas em nenhum outro lugar (variables ​​inúteis) e as remove.
  2. Se você adquirir e liberar o mesmo bloqueio várias vezes (como chamar methods sincronizados do mesmo object), ele poderá adquirir o bloqueio uma vez e fazer todas as chamadas em um único bloco sincronizado.
  3. Se você acessar membros de um object que não são declarados voláteis, ele pode decidir otimizá-lo (colocando valores em registradores e similares), criando resultados estranhos em um código multi-threading.
  4. Ele irá inline methods, para evitar o custo da chamada.
  5. Ele irá traduzir bytecode para código de máquina.
  6. Se o loop for completamente inútil, ele poderá ser completamente removido.

Então, a resposta apropriada para a sua pergunta é que um loop vazio, depois de ser JITed, não leva tempo para ser executado … provavelmente não está mais lá.

Novamente, há muitas outras otimizações, mas, na minha experiência, elas estão entre as que criaram mais dores de cabeça.

Além disso, o JIT está sendo melhorado em qualquer nova versão do Java, e às vezes é um pouco diferente dependendo da plataforma (já que é até certo ponto específico da plataforma). As otimizações feitas pelo JIT são difíceis de entender, porque geralmente não é possível encontrá-las usando o bytecode do javap e do inspecionamento, mesmo que em versões recentes do Java algumas dessas otimizações tenham sido movidas diretamente para o compilador (por exemplo, desde o compilador Java 6 capaz de detectar e avisar sobre variables ​​locais não utilizadas e methods privados).

Se você estiver escrevendo alguns loops para testar algo, geralmente é recomendável ter o loop dentro de um método, chamar o método algumas vezes ANTES de cronometrá-lo, dar um ciclo de “aceleração” e executar o loop cronometrado.

Isso geralmente aciona o JIT em um programa simples como o seu, mesmo que não haja garantia de que ele seja realmente acionado (ou que exista em uma determinada plataforma).

Se você quiser ficar paranóico sobre JIT ou não JIT timing (eu fiz): fazer um primeiro round, cronometrando cada execução do loop, e esperar até o timing se estabilizar (por exemplo, diferença da média menor que 10%), então comece com o seu timing “real”.

O JIT não entra em um pedaço de código até determinar que há algum benefício em fazê-lo. Isso significa que as primeiras passagens através de algum código não serão informadas.