Only Numbers performance test
Tags: csharp, dotnet, netcore, dotnet5
Published: 2021-10-19 02:38:31

Olá pessoal, tudo blz?

Quem nunca se deparou com a necessidade de extrair somente os números de uma string?

Hoje iremos realizar alguns testes de performance utilizando algumas abordagens diferentes para realização dessa tarefa.

Primeiro vamos criar nossa aplicação, ela será um console application em .net 5

```csharp
dotnet new console --name OnlyNumbers
```

Feito isso já temos nossa aplicação criada. Agora vamos instalar dois pacotes nugets que irão nos ajudar.

```csharp
dotnet add package Bogus
dotnet add package BenchmarkDotNet
```

O primeiro pacote será responsável pela criação da nossa massa de dados.

O segundo é responsável por realizar nosso teste de performance e suas métricas.

Feito isso, abra a aplicação em sua IDE de preferência, eu utilizei o Rider da JetBrains.

Com isso, teremos nossa classe Program.cs.

Iremos adicionar mais uma classe com o nome de OnlyNumberTests.cs

Dentro desse arquivo iremos criar uma segunda classe que será a User

```csharp
public class User
{
    public string Login { get; set; }
    public string Password { get; set; }
}
```

Uma classe bem simples contendo somente Login e Password.

De volta a classe OnlyNumberTests, iremos adicionar o annotation **[MemoryDiagnoser]**.

```csharp
[MemoryDiagnoser]
public class OnlyNumberTests
{
}
```

Vamos criar nossa lista de usuários utilizando a biblioteca Bogus da seguinte forma:

```csharp
private static readonly List<User> FakeUsers = new Faker<User>()
    .UseSeed(420)
    .RuleFor(r => r.Login, faker => faker.Person.UserName)
    .RuleFor(r => r.Password, faker => faker.Internet.Password())
    .Generate(10);
```

Vamos criar também o Regex abaixo:

```csharp
private static readonly Regex OnlyNumberPattern =
            new(@"\d+", RegexOptions.IgnoreCase
                        | RegexOptions.Compiled,
                TimeSpan.FromMilliseconds(250));
```

Agora faremos nosso primeiro método de teste, conforme abaixo:

```csharp
[Benchmark]
public void LinqCharIsDigitNewString()
{
    for (var i = 0; i < FakeUsers.Count; i++)
    {
        var numbers = new string(FakeUsers[i].Password.Where(char.IsDigit).ToArray());
    }
}
```

Estamos percorrendo nossa lista de usuários e pesquisando somente pelos caracteres que são dígitos utilizando uma função do próprio .net chamada char.IsDigit().

Feito isso, estamos retornando uma nova string somente com os dígitos.

O segundo método é bem semelhante ao primeiro, somente alteramos a forma como é salva a string ao invés de criar uma nova string utilizando o string.Join():

```csharp
[Benchmark]
public void LinqCharIsDigitStringJoin()
{
    for (var i = 0; i < FakeUsers.Count; i++)
    {
        var numbers = string.Join("", FakeUsers[i].Password.Where(char.IsDigit).ToArray());
    }
}
```

Agora vamos para o método que utilizará o Regex:

```csharp
[Benchmark]
public void RegexMatchOnlyNumberPattern()
{
    for (var i = 0; i < FakeUsers.Count; i++)
    {
        var number = OnlyNumberPattern.Match(FakeUsers[i].Password).Value;
    }
}
```

Nosso último método efetuará validação na "mão":

```csharp
[Benchmark]
public void IfValidator()
{
    for (var i = 0; i < FakeUsers.Count; i++)
    {
        var digitsOnly = string.Empty;
        foreach (var c in FakeUsers[i].Password)
        {
            if (c is >= '0' and <= '9') 
                digitsOnly += c;
        }
    }
}
```

Ele verificará caractere por caractere, caso seja um número ele adiciona a nossa string.

Agora vamos executar nossa aplicação, importante ressaltar que devemos executá-la em modo Release.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kew8k3yv5szkrhmiv84h.png) 

Após executá-la teremos o resultado abaixo no console:

|                      Method |       Mean | Allocated |
|---------------------------- |-----------:|----------:|
|    LinqCharIsDigitNewString | 1,918.3 ns |   2,432 B |
|   LinqCharIsDigitStringJoin | 2,327.8 ns |   3,016 B |
| RegexMatchOnlyNumberPattern | 1,497.0 ns |   2,352 B |
|                 IfValidator |   267.8 ns |     632 B |


Onde é possível verificar que a validação com IF foi mais performática que os outros cenários. Levando 267 nanossegundos tendo uma alocação de 632 Bytes.

Já nosso pior cenário foi com a utilização do linq com string join. Levando 2,3 microssegundos alocando 3KB de memória.

Abaixo link do código no github:
https://github.com/ouell/only-numbers