Economize memória e aumente o desempenho com o ArrayPool<T> do .NET
Tags: csharp, dotnet, dotnetcore, programming
Published: 2023-04-25 17:14:59

A alocação e desalocação de memória podem impactar negativamente o desempenho de uma aplicação. Especialmente em aplicações de alto desempenho, como servidores web, processamento de big data e jogos, a eficiência no gerenciamento de memória é fundamental para garantir uma execução suave e rápida. Felizmente, o dotnet oferece uma solução poderosa e pouco conhecida para otimizar a alocação de memória: o ArrayPool<T>.

## O que é o ArrayPool<T>

O ArrayPool<T> é uma classe que fornece uma maneira eficiente de alocar e reutilizar arrays, evitando a criação e desalocação excessiva de memória. Ele faz parte do namespace System.Buffers e está disponível nas versões do .NET Framework 4.5 e posteriores, bem como no .NET Core e no .NET 5.0 e versões posteriores. É uma classe thread-safe e apesar de sua eficiência comprovada, ainda é pouco utilizado pelos desenvolvedores, o que representa uma grande oportunidade de ganho de desempenho.

## Como funciona o ArrayPool<T>?

O ArrayPool<T> gerencia um pool de arrays de um tipo específico T.
Ao invocar o ArrayPool<T> é criado um bucket com 17 arrays com comprimentos de: 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288 e 1048576, conforme podemos observar nos trechos de códigos abaixo:

![Shared](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x5xggrny1jk8kr9szj0t.png)

![TlsOverPerCoreLockedStacksArrayPool](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vxczfx7b3z8o92i5qjc8.png)

![Utilities](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qternfkccmdttpc7q6ay.png)

A função **GetMaxSizeForBucket** calcula o tamanho máximo para um "bucket" (compartimento) com base no índice do compartimento, usando um deslocamento de bits à esquerda (<<). O operador de deslocamento de bits à esquerda (<<) desloca os bits de um valor para a esquerda, multiplicando o valor por 2 elevado à potência do número de posições de deslocamento. No caso do código, o valor 16 é deslocado para a esquerda pelo número de posições indicado pelo valor de "binIndex".

A linha "int maxSize = 16 << binIndex;" calcula o tamanho máximo do compartimento, onde o tamanho é igual a 16 multiplicado por 2 elevado à potência do valor de "binIndex". Isso implica que o tamanho do compartimento será multiplicado por 2 a cada incremento de "binIndex". Por exemplo, se "binIndex" for 0, o tamanho máximo será 16 (16 << 0 = 16); se "binIndex" for 1, o tamanho máximo será 32 (16 << 1 = 32); se "binIndex" for 2, o tamanho máximo será 64 (16 << 2 = 64), e assim por diante.
 
Quando você solicita uma instância do array usando o método Shared, ele retorna uma instância compartilhada. Em seguida, você pode usar o método Rent para obter um buffer que tenha, pelo menos, o comprimento solicitado. E, ao invés de desalocar o array quando não for mais necessário, você o devolve ao ArrayPool<T> chamando o método Return.

A ideia é que, em vez de alocar e desalocar arrays repetidamente durante a execução de uma aplicação, o ArrayPool<T> reutilize arrays previamente alocados sempre que possível. Isso reduz o tempo de execução e o consumo de recursos, especialmente em cenários em que muitos arrays pequenos precisam ser alocados e desalocados rapidamente.

```csharp
// esqueleto do ArrayPool<T>, onde devemos substituir T pelo tipo

ArrayPool<T> pool = ArrayPool<T>.Shared;
var memory = pool.Rent(20000);
try
{
}
finally
{
  pool.Return(memory);
}
```

## Realizando o Benchmark

Utilizaremos um código muito comum nas aplicações dotnet, onde os desenvolvedores costumam trabalhar com listas para carregarem objetos de uma determinada classe.

![Benchmark](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d9ntdmuatj735vr7gqb5.png)

## Análise do Benchmark

O ArrayPool<T> é mais eficiente trabalhando com arrays maiores, consegue ser 2 vezes mais rápido em alguns casos e com a menor alocação de memória.

## Conclusão

O ArrayPool<T> é uma ferramenta poderosa para otimizar a alocação de memória em aplicações de alto desempenho, e seu uso pode levar a uma execução mais suave e rápida. É importante que os desenvolvedores considerem sua utilização em cenários onde a eficiência no gerenciamento de memória é crítica, visando melhorar o desempenho e a escalabilidade de suas aplicações.