Como faço para escrever um micro-benchmark correto em Java?

Feb 03 2009

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 para se pensar.

Exemplo: O benchmark deve medir o tempo / iteração ou iterações / tempo e por quê?

Relacionado: O benchmarking do cronômetro é aceitável?

Respostas

802 12revs,12users61%EugeneKuleshov Feb 05 2009 at 03:49

Dicas sobre como escrever micro benchmarks dos criadores do Java HotSpot :

Regra 0: Leia um artigo confiável sobre JVMs e micro-benchmarking. Um bom é Brian Goetz, 2005 . Não espere muito de micro-benchmarks; eles medem apenas uma faixa limitada de características de desempenho da JVM.

Regra 1: Sempre inclua uma fase de aquecimento que execute seu kernel de teste por completo, o suficiente para acionar todas as inicializações e compilações antes das fases de tempo. (Menos iterações estão OK na fase de aquecimento. A regra é várias dezenas de milhares de iterações de loop interno.)

Regra 2: Sempre executar com -XX:+PrintCompilation, -verbose:gcetc., 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: Imprimir mensagens no início e no final das fases de cronometragem e aquecimento, para que você possa verificar se não há saída da Regra 2 durante a fase de cronometragem.

Regra 3: Esteja ciente da diferença entre -cliente -server, e OSR e compilações regulares. A -XX:+PrintCompilationbandeira 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 o servidor ao cliente, e regular ao OSR, se você deseja o melhor desempenho.

Regra 4: Esteja ciente dos efeitos de inicialização. Não imprima pela primeira vez durante a fase de temporização, pois a impressão carrega e inicializa classes. Não carregue novas classes fora da fase de aquecimento (ou fase de relatório final), a menos que esteja testando o carregamento de classes especificamente (e, nesse caso, carregue apenas as classes de teste). A regra 2 é sua primeira linha de defesa contra tais efeitos.

Regra 5: Esteja ciente dos efeitos de desotimização e recompilação. Não siga nenhum caminho de código pela primeira vez na fase de temporização, porque o compilador pode 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.

Regra 6: Use ferramentas apropriadas para ler a mente do compilador e espere ser surpreendido pelo código que ele produz. Inspecione o código 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 valores discrepantes. Use -Xbatchpara serializar o compilador com o aplicativo e considere a configuração -XX:CICompilerCount=1para evitar que o compilador seja executado em paralelo consigo mesmo. Faça o possível para reduzir a sobrecarga do GC, defina Xmx(grande o suficiente) igual Xmse use UseEpsilonGCse estiver disponível.

Regra 8: Use uma biblioteca para seu benchmark, pois provavelmente é mais eficiente e já foi depurada para este propósito único. Como JMH , Caliper ou Benchmarks UCSD excelentes de Bill e Paul para Java .

244 AravindYarram Dec 19 2010 at 06:35

Sei que esta pergunta foi marcada como respondida, mas gostaria de mencionar duas bibliotecas que nos ajudam a escrever micro benchmarks

Caliper do Google

Tutoriais de introdução

  1. http://codingjunkie.net/micro-benchmarking-with-caliper/
  2. http://vertexlabs.co.uk/blog/caliper

JMH do OpenJDK

Tutoriais de introdução

  1. Evitando armadilhas de benchmarking na JVM
  2. Usando JMH para Microbenchmarking Java
  3. Introdução ao JMH
88 JonSkeet Feb 03 2009 at 00:46

Coisas importantes para benchmarks Java são:

  • Aqueça o JIT primeiro executando o código várias vezes antes de cronometrá- lo
  • Certifique-se de executá-lo por tempo suficiente para poder medir os resultados em segundos ou (melhor) dezenas de segundos
  • Embora você não possa chamar System.gc()entre as iterações, é uma boa ideia executá-lo entre os 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 seja uma coleta de lixo na minha experiência.)
  • Eu gosto de exibir iterações e tempo, e uma pontuação de tempo / iteração que pode ser escalada de forma que o "melhor" algoritmo obtenha uma pontuação de 1,0 e outros sejam pontuados de maneira relativa. Isso significa que você pode executar todos os algoritmos por um longo tempo, variando o número de iterações e o tempo, mas ainda obtendo resultados comparáveis.

Estou apenas escrevendo um blog sobre o design de uma estrutura de benchmarking em .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.

48 assylias Apr 03 2013 at 19:32

jmh é uma adição recente ao OpenJDK e foi escrito por alguns engenheiros de desempenho da Oracle. Certamente, vale a pena dar uma olhada.

O jmh é um harness Java para construir, executar e analisar benchmarks nano / micro / macro escritos em Java e outras linguagens voltadas para a JVM.

Informações muito interessantes enterradas nos comentários dos testes de amostra .

Veja também:

  • Evitando armadilhas de benchmarking na JVM
  • Discussão sobre os principais pontos fortes do jmh .
23 PeterLawrey Feb 03 2009 at 02:54

O benchmark deve medir o tempo / iteração ou iterações / tempo e por quê?

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.

16 Kip Feb 03 2009 at 00:57

Se você está tentando comparar dois algoritmos, faça pelo menos dois benchmarks para cada um, alternando a ordem. ie:

for(i=1..n)
  alg1();
for(i=1..n)
  alg2();
for(i=1..n)
  alg2();
for(i=1..n)
  alg1();

Eu encontrei algumas diferenças perceptíveis (5-10% às vezes) no tempo de execução do mesmo algoritmo em passagens diferentes.

Além disso, certifique-se de que n seja muito grande, de modo que o tempo de execução de cada loop seja de pelo menos 10 segundos ou mais. Quanto mais iterações, mais números significativos em seu tempo de referência e mais confiáveis ​​são os dados.

15 PeterŠtibraný Feb 03 2009 at 01:00

Certifique-se de usar de alguma forma os resultados calculados no código de referência. Caso contrário, seu código pode ser otimizado.

13 Mnementh Feb 03 2009 at 00:46

Existem muitas armadilhas possíveis para escrever micro-benchmarks em Java.

Primeiro: você tem que calcular com todos os tipos de eventos que levam tempo mais ou menos aleatórios: coleta de lixo, efeitos de cache (de SO para arquivos e de CPU para memória), IO etc.

Segundo: você não pode confiar na precisão dos tempos medidos para intervalos muito curtos.

Terceiro: O JVM otimiza seu código durante a execução. Assim, diferentes execuções na mesma instância JVM se tornarão cada vez mais rápidas.

Minhas recomendações: Faça seu benchmark rodar alguns segundos, o que é mais confiável do que um runtime de milissegundos. Aquecer a JVM (significa executar o benchmark pelo menos uma vez sem medir, para que a JVM possa executar otimizações). E execute seu benchmark várias vezes (talvez 5 vezes) e pegue o valor médio. Execute cada micro-benchmark em uma nova instância JVM (chame para cada benchmark novo Java), caso contrário, os efeitos de otimização do JVM podem influenciar a execução de testes posteriores. Não execute coisas que não são executadas na fase de aquecimento (pois isso poderia acionar o carregamento de classe e a recompilação).

8 SpaceTrucker Jan 21 2013 at 21:04

Também deve ser observado 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 Apode ser mais rápida durante a maioria das execuções do benchmark do que a implementação B. Mas Atambém pode ter um spread maior, de modo que o benefício de desempenho medido de Anão será significativo quando comparado com B.

Portanto, também é importante escrever e executar um micro benchmark corretamente, mas também analisá-lo corretamente.

8 SinaMadani Mar 20 2017 at 02:21

Para complementar o outro conselho excelente, também estaria atento ao seguinte:

Para algumas CPUs (por exemplo, gama Intel Core i5 com TurboBoost), a temperatura (e o número de núcleos atualmente em uso, bem como sua porcentagem de utilização) afeta a velocidade do clock. Como as CPUs são sincronizadas dinamicamente, isso pode afetar seus resultados. Por exemplo, se você tiver um aplicativo de thread único, a velocidade máxima do clock (com TurboBoost) é maior do que para um aplicativo que usa todos os núcleos. Isso pode, portanto, interferir nas comparações de desempenho único e multithread em alguns sistemas. Lembre-se de que a temperatura e a volatilidade também afetam por quanto tempo a frequência do Turbo é mantida.

Talvez um aspecto mais fundamentalmente importante sobre o qual você tenha controle direto: certifique-se de que está medindo a coisa certa! Por exemplo, se você estiver usando System.nanoTime()para comparar um determinado código de referência, coloque as chamadas para a atribuição em lugares que façam sentido para evitar medir coisas nas quais você não está interessado. Por exemplo, não faça:

long startTime = System.nanoTime();
//code here...
System.out.println("Code took "+(System.nanoTime()-startTime)+"nano seconds");

O problema é que você não está obtendo imediatamente o horário de término quando o código termina. Em vez disso, tente o seguinte:

final long endTime, startTime = System.nanoTime();
//code here...
endTime = System.nanoTime();
System.out.println("Code took "+(endTime-startTime)+"nano seconds");
7 Yuriy Dec 19 2010 at 06:22

http://opt.sourceforge.net/Java Micro Benchmark - tarefas de controle necessárias para determinar as características comparativas de desempenho do sistema de computador em diferentes plataformas. Pode ser usado para orientar as decisões de otimização e para comparar diferentes implementações Java.