Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
A MemoryOwner<T> classe é um tipo de buffer que implementa IMemoryOwner<T>. Ele inclui uma propriedade de comprimento inserido e uma série de APIs orientadas a desempenho. É essencialmente um wrapper leve em torno do ArrayPool<T> tipo, com alguns utilitários auxiliares adicionais.
APIs de Plataforma:
MemoryOwner<T>,AllocationMode
Como ele funciona
O MemoryOwner<T> tem os seguintes recursos principais:
- Um dos principais problemas com matrizes retornadas pelas
ArrayPool<T>APIs e comIMemoryOwner<T>instâncias retornadas pelasMemoryPool<T>APIs é que o tamanho especificado é usado apenas como um tamanho mínimo : o tamanho real dos buffers retornados pode ser maior.MemoryOwner<T>resolve esse problema armazenando também o tamanho original solicitado, portantoMemory<T>, eSpan<T>as instâncias recuperadas dele nunca precisam ser fatiadas manualmente. - Quando você usa
IMemoryOwner<T>, obter umSpan<T>para o buffer subjacente exige primeiro obter uma instânciaMemory<T>e, em seguida, umSpan<T>. Esse processo é bastante caro e muitas vezes desnecessário, pois talvez você não precise do intermediárioMemory<T>. Em vez disso,MemoryOwner<T>possui uma propriedade adicionalSpanque envolve diretamente a matriz internaT[]alugada do pool, tornando-a extremamente leve. - O pool não limpa os buffers que aluga por padrão. Se o pool não limpou os buffers quando os retornou anteriormente, eles podem conter dados residuais. Normalmente, você precisa limpar esses buffers alugados manualmente, o que pode ser trabalhoso, especialmente quando feito com frequência.
MemoryOwner<T>oferece uma abordagem mais flexível por meio daAllocate(int, AllocationMode)API. Esse método não apenas aloca uma nova instância exatamente do tamanho solicitado, mas também permite que você especifique qual modo de alocação usar: ou o mesmo queArrayPool<T>, ou um que limpa automaticamente o buffer emprestado. - Às vezes, você pode alugar um buffer com um tamanho maior do que o que realmente precisa e redimensioná-lo. Normalmente, você precisaria alugar um novo buffer e copiar a região de interesse do buffer antigo. Em vez disso,
MemoryOwner<T>expõe umaSlice(int, int)API que retorna uma nova instância encapsulando a área de interesse especificada. Essa abordagem ignora a alocação de um novo buffer e o processo de copiar totalmente os itens.
Sintaxe
Aqui está um exemplo de como alugar um buffer e recuperar uma Memory<T> instância:
// Be sure to include this using at the top of the file:
using Microsoft.Toolkit.HighPerformance.Buffers;
using (MemoryOwner<int> buffer = MemoryOwner<int>.Allocate(42))
{
// Both memory and span have exactly 42 items.
Memory<int> memory = buffer.Memory;
Span<int> span = buffer.Span;
// Writing to the span modifies the underlying buffer.
span[0] = 42;
}
Neste exemplo, você usa um using bloco para declarar o MemoryOwner<T> buffer. Essa abordagem é particularmente útil porque a matriz subjacente é automaticamente retornada para o pool no final do bloco. Se você não tiver controle direto sobre o tempo de vida de uma MemoryOwner<T> instância, o buffer será retornado para o pool quando o coletor de lixo finalizar o objeto. Em ambos os casos, os buffers alugados são sempre retornados corretamente para o pool compartilhado.
Quando você deve usar esse tipo?
Você pode usar MemoryOwner<T> como um tipo de buffer de uso geral. Ele minimiza o número de alocações ao longo do tempo porque reutiliza internamente as mesmas matrizes de um pool compartilhado. Um caso de uso comum é substituir as alocações de matrizes, especialmente quando você faz operações repetidas que exigem um buffer temporário para funcionar ou que resultam em um buffer.
Suponha que você tenha um conjunto de dados que consiste em uma série de arquivos binários e precise ler todos esses arquivos e processá-los de alguma forma. Para separar corretamente o código, você pode escrever um método que simplesmente lê um arquivo binário, que pode ter esta aparência:
public static byte[] GetBytesFromFile(string path)
{
using Stream stream = File.OpenRead(path);
byte[] buffer = new byte[(int)stream.Length];
stream.Read(buffer, 0, buffer.Length);
return buffer;
}
Observe a new byte[] expressão. Se você ler um grande número de arquivos, alocará muitas novas matrizes, o que coloca muita pressão sobre o coletor de lixo. Talvez você queira refatorar esse código usando buffers alugados de um pool, assim como:
public static (byte[] Buffer, int Length) GetBytesFromFile(string path)
{
using Stream stream = File.OpenRead(path);
byte[] buffer = ArrayPool<T>.Shared.Rent((int)stream.Length);
stream.Read(buffer, 0, (int)stream.Length);
return (buffer, (int)stream.Length);
}
Usando essa abordagem, você aluga buffers de um pool, o que significa que, na maioria dos casos, você evita uma alocação. Além disso, como os buffers alugados não são limpos por padrão, você também pode economizar o tempo necessário para preenchê-los com zeros, o que proporciona outra pequena melhoria de desempenho. No exemplo anterior, carregar 1.000 arquivos reduz o tamanho total da alocação de cerca de 1 MB para apenas 1.024 bytes. Você aloca e reutiliza efetivamente apenas um único buffer.
O código anterior tem dois problemas principais:
-
ArrayPool<T>pode retornar buffers que têm um tamanho maior que o tamanho solicitado. Para contornar esse problema, você precisa retornar uma tupla que também indique o tamanho real usado no buffer alugado. - Ao simplesmente retornar uma matriz, você precisa ter cuidado extra para acompanhar corretamente seu tempo de vida e devolvê-la ao pool apropriado. Você pode contornar esse problema usando
MemoryPool<T>em vez disso e retornando umaIMemoryOwner<T>instância, mas ainda tem o problema de buffers alugados terem um tamanho maior do que o necessário. Além disso,IMemoryOwner<T>tem alguma sobrecarga ao recuperar umSpan<T>para trabalhar, devido ao fato de ser uma interface, e o fato de que você sempre precisa obter uma instância deMemory<T>primeiro, e depois umSpan<T>.
Para resolver esses dois problemas, você pode refatorar esse código novamente usando MemoryOwner<T>:
public static MemoryOwner<byte> GetBytesFromFile(string path)
{
using Stream stream = File.OpenRead(path);
MemoryOwner<byte> buffer = MemoryOwner<byte>.Allocate((int)stream.Length);
stream.Read(buffer.Span);
return buffer;
}
A instância retornada IMemoryOwner<byte> descarta o buffer subjacente e o retorna para o pool quando seu IDisposable.Dispose método é invocado. Você pode usá-la para obter uma instância Memory<T> e uma instância Span<T> para interagir com os dados carregados e, em seguida, descartar a instância quando não precisar mais dela. Além disso, todas as MemoryOwner<T> propriedades (como MemoryOwner<T>.Span) respeitam o tamanho inicial solicitado que você usou, portanto, você não precisa mais controlar manualmente o tamanho efetivo dentro do buffer alugado.
Exemplos
Você pode encontrar mais exemplos nos testes de unidade.
.NET Community Toolkit