Como fazer benchmark do seu código

Muitas vezes quando vamos escolher um novo framework ou alguma lib, buscamos benchmarks para que nosso programa tenha a melhor performance possível. Isso é ótimo! Mas você já parou para fazer um benchmark do seu programa para tentar entender onde ele pode ser otimizado? Não sabe como? Então vamos ver como fazer.

A funções de benchmark ficam dentro dos arquivos *_test.go e tem, por convenção, o nome BenchmarkSuaFunc.

Muito similar a quando escrevemos testes, vamos usar o pacote testing do go, mas especificamente vamos usar o testing.B como parâmetro da nossa função de benchmark.

Para começar vamos aproveitar uma das funções do post “Qual a melhor forma para aumentar um array?“.

func AddNomes(nomes []string, pessoas []Pessoa) []string {
    index := len(nomes)

    nomes = append(nomes, make([]string, len(pessoas))...)

    for _, pessoa := range pessoas {
        nomes[index] = pessoa.Nome
        index++
    }

    return nomes
}

Agora, vamos escrever a função que faz o benchmark no nosso código.

func BenchmarkAddNomes(b *testing.B) {
    nomes := []string{"Tiago", "Alexandre", "Luara"}
    pessoas := []Pessoa{
	{Nome: "Dani", Idade: 35},
	{Nome: "Lucas", Idade: 29},
	{Nome: "Rafael", Idade: 38},
    }

    for n := 0; n < b.N; n++ {
        AddNomes(nomes, pessoas)
    }
}

Agora que já escrevemos nossa função de benchmark, vamos executá-lo com o comando:

$ go test -bench=.

Quando passamos um “.” na flag -bench estamos falando para executar todas as funções de benchmark que temos. No entanto, caso a intensão seja executar somente algumas, podemos passar parte do nome das funções, como por exemplo -bench=AddNomes.

O output do comando retorna 3 colunas: NomeDaFunc-NumCPU, Num de Operações, Tempo gasto por operação.

$ BenchmarkAddNomes-4  	 3374858	       373.4 ns/op

Embora aqui já seja possível ter uma idéia de como nossa função está, podemos adicionar mais alguns parâmetros ao comando de execução.

$ go test -bench=AddNomes -cpu=8 -benchmem -benchtime=5s -count 5

Com esse novo comando estamos incrementando o número de cpu de 4 para 8, pedindo para fazer um benchmark da memória (-benchmem), por 5 segundos (-benchtime), 5 vezes (-count).

Agora temos mais 2 colunas em relação a execução anterior. Na quarta coluna temos a quantidade de Bytes por operação e na última coluna a quantidade de alocações de memória executada a cada função.

Com essas informações em mãos, você já consegue ter uma idéia se tem algo que pode ser melhorado em sua aplicação. No entanto, se você for ao post “Qual a melhor forma para aumentar um array?”, verá que em baixo de cada imagem tem um total de memória, porém, como podemos ver na execução acima, em nenhum lugar aparece esse valor.

Para conseguir esse valor, precisamos adicionar uma flag para fazer o profiling da memória. Mas como esse assunto pode ser bem extenso, vou cobrir ele em outro post.

Antes de finalizar, quero ressaltar que para os valores serem precisos, temos que evitar otimizações do compilador. Para isso precisamos armazenar o resultado da função a nível do pacote.

var result []string

func BenchmarkAddNomes(b *testing.B) {
    nomes := []string{"Tiago", "Alexandre", "Luara"}
    pessoas := []Pessoa{
	{Nome: "Dani", Idade: 35},
	{Nome: "Lucas", Idade: 29},
	{Nome: "Rafael", Idade: 38},
    }

    var r []strint
    for n := 0; n < b.N; n++ {
        r = AddNomes(nomes, pessoas)
    }

    result = r
}

Deixem suas dúvidas nos comentários.

Até a próxima!


Subscreva

Fique por dentro de tudo o que acontece no mundo Go.

Deixe uma resposta