Como você escreve (e executa) um micro-benchmark correto em Java?
Estou procurando alguns exemplos de código e comentários que ilustram várias coisas em que pensar.
Exemplo: o benchmark deve medir tempo / iteração ou iterações / tempo e por quê?
Relacionado: O comparador de cronômetro é aceitável?
java
jvm
benchmarking
jvm-hotspot
microbenchmark
John Nilsson
fonte
fonte
Respostas:
Dicas sobre como escrever micro benchmarks dos criadores do Java HotSpot :
Regra 0: Leia um artigo respeitável sobre JVMs e micro-benchmarking. Uma boa é Brian Goetz, 2005 . Não espere muito dos micro-benchmarks; eles medem apenas um intervalo limitado de características de desempenho da JVM.
Regra 1: Sempre inclua uma fase de aquecimento que execute o kernel de teste até o fim, o suficiente para acionar todas as inicializações e compilações antes da temporização das fases. (Menos iterações são boas na fase de aquecimento. A regra geral é várias dezenas de milhares de iterações do loop interno.)
Regra 2: Sempre executar com
-XX:+PrintCompilation
,-verbose:gc
etc., para que possa verificar se o compilador e outras partes da JVM não estão fazendo um trabalho inesperado durante a sua fase de timing.Regra 2.1: Imprima mensagens no início e no final das fases de temporização e aquecimento, para que você possa verificar se não há saída da Regra 2 durante a fase de temporização.
Regra 3: Esteja ciente da diferença entre
-client
e-server
, e OSR e compilações regulares. A-XX:+PrintCompilation
bandeira relata compilações OSR com um sinal de arroba para denotar o ponto de entrada não-inicial, por exemplo:Trouble$1::run @ 2 (41 bytes)
. Prefira servidor ao cliente e regular ao OSR, se você estiver buscando o melhor desempenho.Regra 4: Esteja ciente dos efeitos de inicialização. Não imprima pela primeira vez durante sua fase de temporização, pois a impressão carrega e inicializa as classes. Não carregue novas classes fora da fase de aquecimento (ou fase final do relatório), a menos que você esteja testando o carregamento da classe especificamente (e, nesse caso, carregue apenas as classes de teste). A regra 2 é sua primeira linha de defesa contra tais efeitos.
Artigo 5: Esteja ciente dos efeitos de desoptimização e recompilação. Não pegue nenhum caminho de código pela primeira vez na fase de temporização, porque o compilador pode colocar lixo e recompilar o código, com base em uma suposição otimista anterior de que o caminho não seria usado. A regra 2 é sua primeira linha de defesa contra tais efeitos.
Artigo 6: Use as ferramentas apropriadas para ler a mente do compilador e espere ser surpreendido pelo código que ele produz. Inspecione o código você mesmo antes de formar teorias sobre o que torna algo mais rápido ou mais lento.
Regra 7: Reduza o ruído em suas medições. Execute seu benchmark em uma máquina silenciosa e execute-o várias vezes, descartando discrepâncias. Use
-Xbatch
para serializar o compilador com o aplicativo e considere a configuração-XX:CICompilerCount=1
para impedir que o compilador seja executado em paralelo consigo mesmo. Tente o seu melhor para reduzir a sobrecarga do GC, definirXmx
(suficientemente grande) iguaisXms
e usarUseEpsilonGC
se estiver disponível.Regra 8: use uma biblioteca para seu benchmark, pois provavelmente é mais eficiente e já foi depurado para esse único propósito. Tais como JMH , Caliper ou Bill e Paul's Excellent UCSD Benchmarks para Java .
fonte
System.nanoTime()
não é garantido que seja mais preciso do queSystem.currentTimeMillis()
. Só é garantido que seja pelo menos tão preciso. Geralmente, porém, é substancialmente mais preciso.System.nanoTime()
vez de,System.currentTimeMillis()
é que o primeiro tem a garantia de aumentar monotonicamente. Subtrair os valores retornados duascurrentTimeMillis
invocações pode realmente dar resultados negativos, possivelmente porque a hora do sistema foi ajustada por algum daemon NTP.Eu sei que esta pergunta foi marcada como respondida, mas eu queria mencionar duas bibliotecas que nos ajudam a escrever micro benchmarks
Pinça do Google
Tutoriais de introdução
JMH do OpenJDK
Tutoriais de introdução
fonte
As coisas importantes para os benchmarks Java são:
System.gc()
entre iterações, é uma boa ideia executá-lo entre testes, para que cada teste tenha um espaço de memória "limpo" para trabalhar. (Sim,gc()
é mais uma dica do que uma garantia, mas é muito provável que realmente colete lixo na minha experiência.)Estou apenas no processo de criar um blog sobre o design de uma estrutura de benchmarking no .NET. Eu tenho um par de posts anteriores , que pode ser capaz de lhe dar algumas idéias - nem tudo será apropriado, é claro, mas alguns dos que seja.
fonte
gc
sempre libera memória não utilizada.System.gc()
, como propõe minimizar a coleta de lixo em um teste devido a objetos criados em testes anteriores? Sou pragmático, não dogmático.jmh é uma adição recente ao OpenJDK e foi escrita por alguns engenheiros de desempenho da Oracle. Certamente vale a pena dar uma olhada.
Informações muito interessantes enterradas em comentários dos testes de amostra .
Veja também:
fonte
Depende do que você está tentando testar.
Se você estiver interessado em latência , use tempo / iteração e se estiver interessado em taxa de transferência , use iterações / tempo.
fonte
Se você estiver tentando comparar dois algoritmos, faça pelo menos dois benchmarks para cada um, alternando a ordem. ou seja:
Eu encontrei algumas diferenças visíveis (5-10% às vezes) no tempo de execução do mesmo algoritmo em passes diferentes.
Além disso, verifique se n é muito grande, para que o tempo de execução de cada loop seja de no mínimo 10 segundos. Quanto mais iterações, mais números significativos no tempo de referência e mais confiáveis esses dados.
fonte
De alguma forma, use resultados que são computados no código de referência. Caso contrário, seu código poderá ser otimizado.
fonte
Existem muitas armadilhas possíveis para escrever micro-benchmarks em Java.
Primeiro: você deve calcular com todos os tipos de eventos que levam tempo mais ou menos aleatório: coleta de lixo, efeitos de cache (do sistema operacional para arquivos e da CPU para memória), E / S
Segundo: você não pode confiar na precisão dos tempos medidos por intervalos muito curtos.
Terceiro: a JVM otimiza seu código durante a execução. Portanto, execuções diferentes na mesma instância da JVM se tornarão cada vez mais rápidas.
Minhas recomendações: Faça seu benchmark executar alguns segundos, mais confiável que um tempo de execução em milissegundos. Aqueça a JVM (significa executar o benchmark pelo menos uma vez sem medir, para que a JVM possa executar otimizações). Execute seu benchmark várias vezes (talvez 5 vezes) e aceite o valor mediano. Execute todos os micro-benchmarks em uma nova instância da JVM (chame cada novo Java de benchmark), caso contrário, os efeitos de otimização da JVM podem influenciar os testes posteriores. Não execute coisas que não são executadas na fase de aquecimento (pois isso pode disparar o carregamento da classe e a recompilação).
fonte
Deve-se notar também que também pode ser importante analisar os resultados do micro benchmark ao comparar diferentes implementações. Portanto, um teste de significância deve ser feito.
Isso ocorre porque a implementação
A
pode ser mais rápida durante a maioria das execuções do benchmark do que a implementaçãoB
. MasA
também pode ter um spread mais alto; portanto, o benefício medido do desempenhoA
não terá qualquer significado quando comparado comB
.Portanto, também é importante escrever e executar um micro benchmark corretamente, mas também analisá-lo corretamente.
fonte
Para adicionar aos outros excelentes conselhos, eu também estaria atento ao seguinte:
Para algumas CPUs (por exemplo, a gama Intel Core i5 com TurboBoost), a temperatura (e o número de núcleos atualmente em uso, bem como a porcentagem de utilização) afetam a velocidade do relógio. Como as CPUs têm um clock dinâmico, isso pode afetar seus resultados. Por exemplo, se você tiver um aplicativo de thread único, a velocidade máxima do relógio (com TurboBoost) é maior do que para um aplicativo que usa todos os núcleos. Portanto, isso pode interferir nas comparações de desempenho único e multithread em alguns sistemas. Lembre-se de que a temperatura e as oscilações também afetam por quanto tempo a frequência Turbo é mantida.
Talvez um aspecto mais fundamentalmente importante sobre o qual você tenha controle direto: verifique se está medindo a coisa certa! Por exemplo, se você estiver usando
System.nanoTime()
para comparar um determinado código, faça as chamadas para a tarefa em locais que façam sentido para evitar medir coisas nas quais você não está interessado. Por exemplo, não faça:O problema é que você não está obtendo o horário final imediatamente quando o código termina. Em vez disso, tente o seguinte:
fonte
println
, não uma linha de cabeçalho separada ou algo assim, eSystem.nanoTime()
deve ser avaliada como a primeira etapa na construção da string arg para essa chamada. Não há nada que um compilador possa fazer com o primeiro que ele não possa fazer com o segundo, e nenhum deles os encoraja a fazer um trabalho extra antes de registrar um tempo de parada.http://opt.sourceforge.net/ Java Micro Benchmark - controle as tarefas necessárias para determinar as características de desempenho comparativas do sistema de computador em diferentes plataformas. Pode ser usado para orientar decisões de otimização e comparar diferentes implementações Java.
fonte