![]() |
Neste artigo veremos como usar o recurso RecyblableMemoryStream do C#. |
A biblioteca RecyblableMemoryStream do namespace
Microsoft.IO fornecer um pool de objetos
MemoryStream para melhorar o desempenho do aplicativo, especialmente na área
de coleta de lixo.
Ela é uma biblioteca
de alto desempenho projetada para melhorar o desempenho do aplicativo ao
trabalhar com streams sendo um substituto para
MemoryStream e oferece melhor desempenho do que instâncias
MemoryStream.
Podemos usar a biblioteca RecyclableMemoryStream
para eliminar alocações de objetos muito grandes na memória - LOH - Large
Objects Heap - e evitar falhas e fragmentação da memória.
A RecyclableMemoryStream melhora o desempenho do Garbage Collector , garantindo que os buffers maiores usados para os streams sejam colocados na heap da geração 2 e permaneçam lá para sempre. Isso deve fazer com que as coletas completas aconteçam com menos frequência. Se você escolher tamanhos de buffer acima de 85.000 bytes, garantirá que eles sejam colocados no heap de objetos grandes, que é acionado com ainda menos frequência pelo coletor de lixo.
Para usar os recursos desta biblioteca basta instalar o pacote nuget no seu projeto:
Install-Package Microsoft.IO.RecyclableMemoryStream
A classe RecyclableMemoryStreamManager mantém dois conjuntos separados de objetos:
Small Pool - Contém buffers pequenos (de
tamanho configurável). Usado por padrão para todas as operações normais
de leitura/gravação. Vários buffers pequenos são encadeados na classe
RecyclableMemoryStream e abstraídos em um único stream;
Large Pool - Contém buffers grandes, que são usados apenas quando você deve ter um único buffer contíguo, como quando planeja chamar GetBuffer(). É possível criar streams maiores do que é possível ser representado por um único buffer devido aos limites de tamanho de array do .NET; (Aqui temos duas versões : Linear(default) e Exponential)
Nota: Múltiplos Small Pools de 128 KB
cada e Large Pools de 1 MB cada (padrão) são criados.
O Large Pool possui duas versões :
O Large Pool Linear que é o padrão e cresce linearmente,
O Large Pool Exponential que cresce de maneira exponencial, ou seja, os buffers dobram de tamanho para cada slot.
Uma instância RecyclableMemoryStream
começa alocando inicialmente um pequeno buffer. Buffers adicionais são
encadeados à medida que a capacidade do stream aumenta.
Normalmente, o pool pequeno é usado para operações normais de leitura/gravação e
o uso de memória é otimizado e eficiente porque os buffers são abstraídos. É por
isso que o RecyclableMemoryStream é muito mais eficiente que o
MemoryStream.
O pool grande é usado apenas quando o aplicativo precisa de um bloco de memória
contíguo.
Quando você chama o método GetBuffer usando o código mostrado abaixo, os buffers
pequenos são convertidos em um buffer único, grande e contíguo.
var buffer =
reciclávelMemoryStreamManager.GetStream().GetBuffer();
Para mais detalhes confira o link:
https://github.com/microsoft/Microsoft.IO.RecyclableMemoryStream
Usando RecyblableMemoryStream
Para mostrar o uso deste recurso vamo Serializar e Zipar um grande objeto que será uma coleção muito grande.
Para gerar a coleção de teste vamos usar o Bogus, e vamos usar o código definido em “The Great C# Example” onde é demonstrado os recursos suportados desta biblioteca. Com base neste exemplo vamos gerar uma coleção de usuários aleatórios que será usada para realizar os benchmarks.
Assim vamos criar um projeto do tipo Console no .NET 6 chamado CSharp_RecyclabeMS.
A seguir vamos instalar a biblioteca do Bogus : install-package Bogus
Vamos criar a classe ColecaoPessoas no projeto e incluir o código para gerar a coleção fake de pessoas que iremos usar para testar. O código usado aqui voi retirado do exemplo do site oficial do Bogus.
O código usado para Serializar e Zipar a coleção pode ser definido da seguinte forma:
public async Task SerializeMemoryStream()
{
using (var stream = new MemoryStream())
{
using (var gzip = new GZipStream(stream, CompressionLevel.Optimal, true))
{
await JsonSerializer.SerializeAsync(gzip, _users);
await gzip.FlushAsync();
}
}
}
|
Neste código estamos criando um MemoryStream e um GZipStream e, em seguida, serializamos a coleção de usuários no stream zip e liberamos.
Agora vamos realizar a mesma tarefa usando agora RecyclableMemoryStreamManager :
private RecyclableMemoryStreamManager _memoryStreamManager;
public async Task SerializeRecyclableMemoryManager()
{
using (var stream = _memoryStreamManager.GetStream("memory"))
{
using (var gzip = new GZipStream(stream, CompressionLevel.Optimal, true))
{
await JsonSerializer.SerializeAsync(gzip, _users);
await gzip.FlushAsync();
}
}
}
|
Temos praticamente o mesmo código, exceto que ao invés de usar um MemoryStream estamos fazendo uso do RecyclabeMemoryStreamManager.
Para poder testar e comparar o desempenho das duas implementações vamos usar os recursos da biblioteca BenchmarkDotNet. Assim vamos instalar no projeto o pacote : install-Package BenchmarkDotNet
Você pode consultar a documentação deste recurso neste link: https://fransbouma.github.io/BenchmarkDotNet/GettingStarted.htm
Veja também meu artigo sobre o assunto neste link: https://www.macoratti.net/21/11/c_benchmark1.htm
Agora vamos criar a classe BenchMark1 no projeto usando o código abaixo:
using BenchmarkDotNet.Attributes;
using Microsoft.IO;
using System.IO.Compression;
using System.Text.Json;
namespace CSharp_RecyclableMS;
[MemoryDiagnoser]
public class BenchMark1
{
private RecyclableMemoryStreamManager? _memoryStreamManager;
private List<User>? _users;
[GlobalSetup]
public void Setup()
{
_memoryStreamManager = new RecyclableMemoryStreamManager();
_users = ColecaoPessoas.Generate();
}
[Benchmark]
public async Task SerializeRecyclableMemoryManager()
{
using (var stream = _memoryStreamManager.GetStream("memory"))
{
using (var gzip = new GZipStream(stream, CompressionLevel.Optimal, true))
{
await JsonSerializer.SerializeAsync(gzip, _users);
await gzip.FlushAsync();
}
}
}
[Benchmark]
public async Task SerializeMemoryStream()
{
using (var stream = new MemoryStream())
{
using (var gzip = new GZipStream(stream, CompressionLevel.Optimal, true))
{
await JsonSerializer.SerializeAsync(gzip, _users);
await gzip.FlushAsync();
}
}
}
}
|
Estamos usando uma coleção de objetos User que contém 8.000 elementos e terá os mesmos dados para ambos os métodos, pois usamos a mesma semente para gerá-los. (Veja o método Generate da classe ColecaoPessoas)
Para concluir vamos incluir o código abaixo na classe Program para iniciar a execução do nosso benckmark:
using BenchmarkDotNet.Running;
using CSharp_RecyclableMS;
Console.WriteLine("## Usando RecyclableMemoryStream ##\n");
BenchmarkRunner.Run<BenchMark1>();
Console.ReadKey();
|
Executando o projeto teremos o seguinte resultado :
Analisando os resultados podemos conferir um melhor desempenho do Garbage Collector na alocação dos objetos na memória.
Práticas recomendadas do RecyclableMemoryStream
A fragmentação de memória pode afetar o desempenho de seu aplicativo, e o grande
heap de objetos no .NET é propenso à fragmentação. As seguintes diretrizes ou
práticas recomendadas devem ser seguidas ao trabalhar com o
RecyclableMemoryStream:
Defina as propriedades blockSize, largeBufferMultiple, maxBufferSize, MaximumFreeLargePoolBytes e MaximumFreeSmallPoolBytes para os valores apropriados;
Descarte qualquer objeto de fluxo assim que terminar de usá-lo;
Nunca chame o método ToArray();
Evite chamar o método GetBuffer();
O Microsoft.IO.RecyclableMemoryStream é um alocador de streams de memória em pool que é especialista em reduzir a carga de GC e melhorar o desempenho de seus aplicativos, e aproveita os buffers agrupados para eliminar as alocações de heap de objeto grandes (LOH). Ele não apenas evita a fragmentação de memória e vazamentos de memória, mas também fornece métricas que podem ser usadas para rastrear o desempenho.
Pegue o projeto aqui :
CSharp_RecyclableMS.zip (sem as
referências)...
"Não sabeis vós que os que correm no estádio, todos,
na verdade, correm, mas um só leva o prêmio? Correi de tal maneira que o
alcanceis. E todo aquele que luta de tudo se abstém; eles o fazem para alcançar
uma coroa corruptível; nós, porém, uma incorruptível."
1 Coríntios 9:24,25
Referências: