Vira e mexe precisamos testar o tempo de algo que roda em linha de comando, seja uma requisição através de cURL ou algum scripts/aplicação que construímos.

O jeito mais simples e onipresente nas interfaces de linha de comando do Linux é o time. Ele executa o comando passado para ele através de parâmetro e diz qual foi o tempo de execução:

time python fibonacci.py Resultado da execução do time

O time pode ajudar muito, principalmente quando queremos comparar escalas de tempo muito diferentes, por exemplo uma mudança de código que mudou o tempo de execução de 1s para 100ms. Porém como a medida pode variar muito de execução devido a diversos fatores (cache, resolução de DNS, etc) testar apenas uma única vez pode não trazer resultados muito acurados, pois não temos tempo médio nem a variância do tempo. Para isso encontrei uma outra aplicação chamada hyperfine.

O hyperfine executa diversas vezes o comando e traz algumas estatísticas completas da execução, testando com o mesmo script que usamos acima temos:

lucaspolo@notebook:~$ hyperfine python fibonacci.py 
Benchmark #1: python
  Time (mean ± σ):      44.9 ms ±   4.5 ms    [User: 50.8 ms, System: 8.2 ms]
  Range (min … max):    42.1 ms …  71.3 ms    41 runs
 
  Warning: The first benchmarking run for this command was significantly slower than the rest (71.3 ms). This could be caused by (filesystem) caches that were not filled until after the first run. You should consider using the '--warmup' option to fill those caches before the actual benchmark. Alternatively, use the '--prepare' option to clear the caches before each timing run.
 
Benchmark #2: fibonacci.py
Error: Command terminated with non-zero exit code. Use the '-i'/'--ignore-failure' option if you want to ignore this. Alternatively, use the '--show-output' option to debug what went wrong.
lucaspolo@notebook:~$ 

No resultado acima já podemos ver com mais precisão o que aconteceu. Vimos que o script rodou com o tempo de 44.9ms com o desvio padrão de 4.5ms, além do teste de menor tempo e maior tempo. Outro ponto que foi alertado para nós é que a primeira execução levou o tempo 71.3ms, possivelmente por algum cache na hora de carregar o arquivo em disco na primeira vez, o que também poderia acontecer ao fazer requisições e afins (como falei, podemos ter caches de DNS sendo esquentados e muitos outros fatores de I/O). Para ajudar nisso podemos rodar o hyperfine com um parâmetro que faz executar algumas vezes o processo antes de efetivamente iniciar a contagem, “esquentando” assim o nosso ambiente:

lucaspolo@notebook:~$ hyperfine --warmup 3 python fibonacci.py 
Benchmark #1: python
  Time (mean ± σ):      44.2 ms ±   1.8 ms    [User: 49.1 ms, System: 8.8 ms]
  Range (min … max):    41.2 ms …  51.0 ms    67 runs
 
Benchmark #2: fibonacci.py
Error: Command terminated with non-zero exit code. Use the '-i'/'--ignore-failure' option if you want to ignore this. Alternatively, use the '--show-output' option to debug what went wrong.

Como agora esquentamos melhor o ambiente a variância diminui bastante e temos uma visão mais realista do tempo de execução.

Estes são apenas alguns pontos básicos de como o hyperfine pode ajudar em benchmarks de ferramentas de linha de comando, no seu repositório é possível encontrar muitas outras funcionalidades, como parametrização de entrada do comando, execução de comandos de preparação para cada teste (as vezes queremos justamente testar o tempo sem caches, ai podemos limpar antes) e muito mais.

O mais importante neste ponto não é a ferramenta utilizada em si, mas a busca por métricas que possam embasar melhor nossas decisões.