C# -  Usando a biblioteca BenchmarkDotNet


Hoje veremos como usar a biblioteca BenchmarkDotNet para avaliação de desempenho do seu código.

A biblioteca BenchmarkDotNet é uma biblioteca .NET leve, de código aberto e poderosa que pode transformar seus métodos em benchmarks, rastrear esses métodos e, em seguida, fornecer insights sobre os dados de desempenho capturados.

Usando esta biblioteca fica mais fácil escrever benchmarks e os resultados do processo de benchmarking também são fáceis de usar. Assim você pode usar essa biblioteca para realizar testes de avaliação de desempenho de novas funcionalidades ou de novas implementações comparando-as com as anteriores.

Se você não sabe o que é benchmark, veja a definição da Wikipédia :

'..Benchmark é o ato de executar um programa de computador, um conjunto de programas ou outras operações, a fim de avaliar o desempenho relativo de um objeto, normalmente executando uma série de testes padrão e ensaios nele. "

De uma forma menos formal podemos dizer que benchmark é o ato de comparar de forma eficiente o desempenho entre dispositivos, programas, códigos, etc., utilizando um ou mais programas ou ferramentas, para avaliar o tempo gasto em sua execução. Para conseguir compará-los de maneira equivalente, é preciso realizar uma série de testes e analisar inúmeros dados diferentes.

Assim como um desenvolvedor geralmente estaremos realizando medidas de desempenho na execução de código e neste contexto podemos dizer que um benchmark é uma medida ou um conjunto de medidas relacionadas ao desempenho de uma parte do código em um aplicativo.

O código de benchmarking é essencial para entender as métricas de desempenho dos métodos em seu aplicativo. É sempre uma boa abordagem ter as métricas em mãos ao otimizar o código. É muito importante para nós sabermos se as mudanças feitas no código melhoraram ou pioraram o desempenho. O benchmarking também ajuda a restringir as partes do código no aplicativo que precisam de refatoração.

Você pode consultar a documentação neste link:  https://fransbouma.github.io/BenchmarkDotNet/GettingStarted.htm

Neste artigo iremos mostrar como usar a biblioteca BenchmarkDotNet para realizar a avaliação de desempenho na execução de código na linguagem C#.

Usando BenchmarkDotNet

Vamos criar um novo projeto do tipo Console usando o Visual Studio 2022 chamado CShp_Benchmark1.

A seguir vamos instalar o pacote BenchmarkDotNet no projeto usando a janela do Package Manager Console :

install-Package BenchmarkDotNet

Ou usando o menu Tools -> ..-> Manage Nuget Package for Solutions no VS 2022 :

Ao final o nosso projeto terá as seguintes referências :

A seguir podemos usar a biblioteca BenchmarkDotNet em nosso projeto. Para isso devemos seguir os seguintes passos:

Para mostrar uma utilização básica do uso desta biblioteca vamos criar a classe MemoriaBenchmarkDemo em nosso projeto a incluir o código abaixo nesta classe:

using BenchmarkDotNet.Attributes;
using System.Text;
namespace CShp_Benchmark1
{
    [MemoryDiagnoser]
    public class MemoriaBenchmarkDemo
    {
        int NumeroDeItens = 1000;

        [Benchmark]
        public string ConcatenandoStringsCom_StringBuilder()
        {
            var sb = new StringBuilder();
            for (int i = 0; i < NumeroDeItens; i++)
            {
                sb.Append("Macoratti.net_" + i);
            }
            return sb.ToString();
        }
        [Benchmark]
        public string ConcatStringsUsando_GenericList()
        {
            var list = new List<string>(NumeroDeItens);
            for (int i = 0; i < NumeroDeItens; i++)
            {
                list.Add("Macoratti.net_" + i);
            }
            return list.ToString();
        }
    }
}

No código acima temos a classe onde usamos o atributo [MemoryDiagnoser] que vai diagnosticar a alocação de memória na execução do código.

A seguir temos dois métodos que realizam a concatenação de strings 1000 vezes cujo desempenho desejamos comparar. Em cada método usamos o atributo Benchmark que define o que deve ser medido e avaliado, e a invocação de um método que possui o atributo Benchmark definido é conhecida como uma operação.

A seguir podemos definir o código na classe Program para realizar o benckmark e comparar e avaliar os resultados. Para isso inclua o código abaixo na classe Program:

using BenchmarkDotNet.Running;
using CShp_Benchmark1;
Console.WriteLine("### Usando BenchmarkDotNet  ###\n");
Console.WriteLine("Pressione algo para iniciar\n");
Console.ReadLine();
var resultado = 
   BenchmarkRunner.Run<MemoriaBenchmarkDemo>();

 

No código acima devemos especificar o ponto de partida inicial - a classe BenchmarkRunner. Esta é uma forma de informar o BenchmarkDotNet para executar benchmarks na classe especificada.

Executando o projeto no modo DEBUG iremos obter o seguinte resultado:

Note que obtemos um erro e temos a mensagem nos alertando que projeto deve ser executado no modo RELEASE.

Assim ao realizar o benchmarking, você deve sempre assegurar-se de executar seu projeto no Release. O motivo é que, durante a compilação, o código é otimizado de maneira diferente para os modos de depuração e de release. O compilador C# faz algumas otimizações no modo Release que não estão disponíveis no modo de depuração.

Assim se estiver usando a linha de comando para executar no modo release emita o comando:

dotnet run -p <nome_projeto>.csproj -c Release

Vamos usar a linha de comando para visualizar a exibição dos resultados. Para isso vamos entrar na pasta do projeto e emitir o comando:

dotnet run CShp_Benchmark1.csproj -c Release

Para obter melhores resultados, você deve certificar-se de que todos os aplicativos sejam fechados e todos os processos desnecessários parados antes de executar benchmarks.

Observe que, se você não especificar o parâmetro de configuração, o runtime tentará fazer benchmarking em código não otimizado em modo de depuração. E você verá o mesmo erro mostrado na figura.

Analise os resultados do benchmarking

Assim que a execução do processo de benchmarking for concluída, um resumo dos resultados será exibido na janela do console. A seção de resumo contém informações relacionadas ao ambiente em que os benchmarks foram executados, como a versão do BenchmarkDotNet, sistema operacional, hardware do computador, versão .NET, informações do compilador e informações relacionadas ao desempenho da aplicação.

Alguns arquivos também serão criados na pasta BenchmarkDotNet.Artifacts na pasta raiz do aplicativo.

Aqui está um resumo dos resultados obtidos no meu ambiente para o teste realizado :

Como fica evidente no resumo mostrado na Figura acima, para cada método de comparação, você verá uma linha de dados que especifica as métricas de desempenho, como tempo médio de execução, Gen 0, Gen 1, Gen 2 coleções, etc.

Ao examinar os resultados mostrados na figura, você pode ver que ConcatStringUsando_GenericList é muito mais rápido do que o método ConcatenandoStringCom_StringBuilder, e, além disto , este método realizar mais alocações.

Agora vamos incluir o atributo RankColumn no topo da classe MemoriaBenchmarkDemo.

[RankColumn]
[MemoryDiagnoser]

public class MemoriaBenchmarkDemo
{
    ...
}

Isso vai incluir uma coluna extra à saída do resultado, indicando qual método foi mais rápido. Execute o processo de benchmarking novamente usando o comando: dotnet run CShp_Benchmark1.csproj -c Release

A seguir temos o resultado obtido:

Note que agora temos a coluna Rank indicando que o método ConcatStringUsando_GenericList esta em primeiro lugar indicando que é mais rápido.

Dessa forma temos que a ferramenta BenchmarkDotNet é uma boa ferramenta para realizar avaliações de desempenho e que fornece uma maneira simples e sem custo de tomar uma decisão sobre as métricas de desempenho de seu aplicativo.

Pegue o projeto aqui:  CShp_Benchmark1.zip (sem as referências)

"Então os justos resplandecerão como o sol, no reino de seu Pai. Quem tem ouvidos para ouvir, ouça."
Mateus 13:43

Referências:


José Carlos Macoratti