Como você analisa a performance do seu código?

O benchmarking é uma técnica essencial no desenvolvimento de software para medir e comparar o desempenho de diferentes trechos de código. Ele permite identificar gargalos de desempenho e otimizar o código, garantindo que ele execute de maneira eficiente. O C# possui várias ferramentas e bibliotecas para realizar benchmarks de forma fácil e precisa, uma delas é a biblioteca BenchmarkDotNet.

O BenchmarkDotNet é uma poderosa biblioteca de benchmarking para o .NET que oferece recursos avançados, como medição precisa de tempo, configuração automática do ambiente de benchmark e geração de relatórios detalhados.

Exemplo de Utilização

Vamos ver um exemplo prático de como usar o BenchmarkDotNet para medir o desempenho da validação de CPF em C#.

#1 Configuração do Projeto

Crie um novo projeto de Console no Visual Studio ou em sua linha de comando preferida:

Plaintext
dotnet new console -n Benchmark
cd Benchmark

Em seguida, instale o pacote NuGet do BenchmarkDotNet:

Plaintext
dotnet add package BenchmarkDotNet

#2 Implementação da Validação de CPF

Agora, implementaremos a lógica de validação de CPF em duas versões.

Versão 1

Versão facilmente encontrada na internet e implementada em muitos projetos.

C#
namespace Benchmark.Versoes
{
    internal class CPFVersao1
    {
        public static bool ValidarCPF(string sourceCPF)
        {
            if (String.IsNullOrWhiteSpace(sourceCPF))
                return false;

            string clearCPF;
            clearCPF = sourceCPF.Trim();
            clearCPF = clearCPF.Replace("-", "");
            clearCPF = clearCPF.Replace(".", "");

            if (clearCPF.Length != 11)
            {
                return false;
            }

            int[] cpfArray;
            int totalDigitoI = 0;
            int totalDigitoII = 0;
            int modI;
            int modII;

            if (clearCPF.Equals("00000000000") ||
                clearCPF.Equals("11111111111") ||
                clearCPF.Equals("22222222222") ||
                clearCPF.Equals("33333333333") ||
                clearCPF.Equals("44444444444") ||
                clearCPF.Equals("55555555555") ||
                clearCPF.Equals("66666666666") ||
                clearCPF.Equals("77777777777") ||
                clearCPF.Equals("88888888888") ||
                clearCPF.Equals("99999999999"))
            {
                return false;
            }

            foreach (char c in clearCPF)
            {
                if (!char.IsNumber(c))
                {
                    return false;
                }
            }

            cpfArray = new int[11];
            for (int i = 0; i < clearCPF.Length; i++)
            {
                cpfArray[i] = int.Parse(clearCPF[i].ToString());
            }

            for (int posicao = 0; posicao < cpfArray.Length - 2; posicao++)
            {
                totalDigitoI += cpfArray[posicao] * (10 - posicao);
                totalDigitoII += cpfArray[posicao] * (11 - posicao);
            }

            modI = totalDigitoI % 11;
            if (modI < 2) { modI = 0; }
            else { modI = 11 - modI; }

            if (cpfArray[9] != modI)
            {
                return false;
            }

            totalDigitoII += modI * 2;

            modII = totalDigitoII % 11;
            if (modII < 2) { modII = 0; }
            else { modII = 11 - modII; }
            if (cpfArray[10] != modII)
            {
                return false;
            }
            // CPF Válido!
            return true;
        }
    }
}

Versão 2

Está versão foi otimizada para redução do uso do Garbage Collection (GC), tendendo a diminuir o uso de memória e aumento da velocidade de execução.

C#
namespace Benchmark.Versoes
{
    internal class CPFVersao2
    {
        public struct Cpf
        {
            private readonly string _value;

            public readonly bool EhValido;
            private Cpf(string value)
            {
                _value = value;

                if (value == null)
                {
                    EhValido = false;
                    return;
                }

                var posicao = 0;
                var totalDigito1 = 0;
                var totalDigito2 = 0;
                var dv1 = 0;
                var dv2 = 0;

                bool digitosIdenticos = true;
                var ultimoDigito = -1;

                foreach (var c in value)
                {
                    if (char.IsDigit(c))
                    {
                        var digito = c - '0';
                        if (posicao != 0 && ultimoDigito != digito)
                        {
                            digitosIdenticos = false;
                        }

                        ultimoDigito = digito;
                        if (posicao < 9)
                        {
                            totalDigito1 += digito * (10 - posicao);
                            totalDigito2 += digito * (11 - posicao);
                        }
                        else if (posicao == 9)
                        {
                            dv1 = digito;
                        }
                        else if (posicao == 10)
                        {
                            dv2 = digito;
                        }

                        posicao++;
                    }
                }

                if (posicao > 11)
                {
                    EhValido = false;
                    return;
                }

                if (digitosIdenticos)
                {
                    EhValido = false;
                    return;
                }

                var digito1 = totalDigito1 % 11;
                digito1 = digito1 < 2
                    ? 0
                    : 11 - digito1;

                if (dv1 != digito1)
                {
                    EhValido = false;
                    return;
                }

                totalDigito2 += digito1 * 2;
                var digito2 = totalDigito2 % 11;
                digito2 = digito2 < 2
                    ? 0
                    : 11 - digito2;

                EhValido = dv2 == digito2;
            }

            public static implicit operator Cpf(string value)
                => new Cpf(value);

            public override string ToString() => _value;
        }

        public static bool ValidarCPF(Cpf sourceCPF) =>
            sourceCPF.EhValido;
    }
}

#3 Configuração do Benchmark

Agora, criaremos uma classe chamada ValidarCPFBenchmark que conterá os métodos de benchmark para validar CPFs válidos e inválidos nas duas implementações. Configuramos para que a versão 1 seja a base de comparação e que os resultados sejam ordenados do mais rápido para o mais lento.

C#
using Benchmark.Versoes;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Mathematics;
using BenchmarkDotNet.Order;

namespace Benchmark
{
    [Orderer(SummaryOrderPolicy.FastestToSlowest)]
    [RankColumn(NumeralSystem.Arabic)]
    public class ValidarCPFBenchmark
    {
        [Params("12345678909", "11111111111")]
        public string CPF { get; set; }

        [Benchmark(Baseline = true)]
        public void ValidarCPFVersao1()
        {
            CPFVersao1.ValidarCPF(CPF);
        }
        [Benchmark]
        public void ValidarCPFVersao2()
        {
            CPFVersao2.ValidarCPF(CPF);
        }
    }
}

#4 Execução do Benchmark

No Program.cs, executaremos o benchmark usando a classe BenchmarkRunner do BenchmarkDotNet:

C#
using Benchmark;
using BenchmarkDotNet.Running;

var summary = BenchmarkRunner.Run<ValidarCPFBenchmark>();

#5 Executar o Benchmark

No terminal, execute o o comando abaixo:

Plaintext
dotnet run --configuration Release

#6 Resultado do Benchmark e Significados

Após executar o exemplo, obteremos um resultado semelhante ao seguinte:

Plaintext
BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19045.3086/22H2/2022Update)
Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
.NET SDK=7.0.203
  [Host]     : .NET 6.0.18 (6.0.1823.26907), X64 RyuJIT AVX2
  DefaultJob : .NET 6.0.18 (6.0.1823.26907), X64 RyuJIT AVX2


|            Method |         CPF |      Mean |    Error |   StdDev | Ratio | RatioSD | Rank |
|------------------ |------------ |----------:|---------:|---------:|------:|--------:|-----:|
| ValidarCPFVersao2 | 11111111111 |  30.83 ns | 0.210 ns | 0.186 ns |  0.93 |    0.02 |    1 |
| ValidarCPFVersao1 | 11111111111 |  33.08 ns | 0.678 ns | 0.807 ns |  1.00 |    0.00 |    2 |
|                   |             |           |          |          |       |         |      |
| ValidarCPFVersao2 | 12345678909 |  32.74 ns | 0.164 ns | 0.153 ns |  0.13 |    0.00 |    1 |
| ValidarCPFVersao1 | 12345678909 | 254.31 ns | 3.838 ns | 3.590 ns |  1.00 |    0.00 |    2 |

Os resultados são apresentados em uma tabela, onde encontramos os seguintes significados:

Method: Nome do método de benchmark.
Mean: Tempo médio de execução para o método em questão, expresso em unidades de tempo (por exemplo, microssegundos).
Error: Valor do erro absoluto para o tempo médio de execução.
StdDev: Desvio padrão para o tempo médio de execução.
Ratio: Indica a relação entre os tempos de execução e a referência.
RatioSD: Representa o desvio padrão da relação entre os tempos de execução e a referência.
Rank: Ordenação do mas lento para o mais rápido

Analise do Resultado

Podemos verificar que para o CPF Inválido, pela média de tempo, as duas implementações são próximas, mas quando comparamos um CPF válido, a diferença é muito grande, chegando a ser quase 8X mais rápido na média.

Neste exemplo analisamos apenas tempo de execução, mas a biblioteca traz uma infinidade de indicadores que podem ser utilizado na análise.

Lembre-se de que o benchmarking deve ser utilizado como uma ferramenta complementar no processo de otimização do código. É importante considerar fatores adicionais, como legibilidade, manutenibilidade e requisitos específicos do projeto, ao tomar decisões de otimização.

Espero que este exemplo tenha sido útil e que você possa aplicar o benchmarking em seus projetos para melhorar o desempenho do código em C#.

Código Fonte do exemplo:

Documentação do BenchmarkDotNet:

Código do exemplo baseado no seguinte post:

Compartilhe:

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Rolar para cima