Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
A MemoryOwner<T> classe é um tipo de buffer que implementa IMemoryOwner<T>. Inclui uma propriedade de comprimento embutida e uma série de APIs orientadas para o desempenho. É essencialmente um wrapper leve em torno do tipo ArrayPool<T>, com algumas ferramentas auxiliares adicionais.
APIs da Plataforma:
MemoryOwner<T>,AllocationMode
Como funciona
MemoryOwner<T> possui as seguintes características principais:
- Um dos principais problemas com os arrays retornados pelas
ArrayPool<T>APIs e as instâncias retornadas pelasMemoryPool<T>APIs é que o tamanho que especifica é usado apenas como tamanho mínimo: o tamanho real dos buffers retornados pode ser maior.MemoryOwner<T>resolve este problema ao também armazenar o tamanho original solicitado, de modo queMemory<T>Span<T>as instâncias recuperadas nunca precisam de ser cortadas manualmente. - Quando usas
IMemoryOwner<T>, obter umSpan<T>para o buffer subjacente requer primeiro obter umaMemory<T>instância, e depois umSpan<T>. Este processo é bastante caro e muitas vezes desnecessário, pois pode não precisar do intermédioMemory<T>. Em vez disso,MemoryOwner<T>tem uma propriedade adicionalSpanextremamente leve porque envolve diretamente o conjunto internoT[]alugado da piscina. - A piscina não limpa os buffers que aluga por padrão. Se o pool não limpou os buffers quando os devolveu anteriormente, podem conter dados indesejados. Normalmente, é necessário limpar manualmente estes buffers alugados, o que pode ser complicado, especialmente quando feito com frequência.
MemoryOwner<T>oferece uma abordagem mais flexível através daAllocate(int, AllocationMode)API. Este método não só aloca uma nova instância exatamente do tamanho solicitado, como também permite especificar qual o modo de alocação a usar: ou o mesmo queArrayPool<T>, ou um que limpa automaticamente o buffer alugado. - Por vezes, pode alugar um buffer com um tamanho maior do que o que realmente precisa e depois redimensioná-lo. Normalmente, terias de 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 devolve uma nova instância que envolve a área de interesse especificada. Esta abordagem evita o aluguer de um novo buffer e copiar completamente 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, usas um using bloco para declarar o MemoryOwner<T> buffer. Esta abordagem é particularmente útil porque o array subjacente é automaticamente devolvido ao pool no final do bloco. Se não tiver controlo direto sobre a vida útil de uma MemoryOwner<T> instância, o buffer é devolvido ao pool quando o coletor de lixo finaliza o objeto. Em ambos os casos, os buffers alugados são sempre corretamente devolvidos ao pool partilhado.
Quando deve usar este tipo?
Podes usar MemoryOwner<T> como um tipo de buffer de uso geral. Minimiza o número de alocações ao longo do tempo porque reutiliza internamente os mesmos arrays de um pool partilhado. Um caso de uso comum é substituir new T[] alocações de arrays, especialmente quando se fazem operações repetidas que requerem um buffer temporário para trabalhar ou que produzem um buffer como resultado.
Suponha que tem um conjunto de dados composto por uma série de ficheiros binários, e precisa de ler todos esses ficheiros e processá-los de alguma forma. Para separar corretamente o seu código, pode escrever um método que simplesmente leia um ficheiro binário, que pode assemelhar-se a isto:
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 leres um grande número de ficheiros, alocas muitos novos arrays, o que coloca muita pressão no coletor de lixo. Pode querer refatorar este código usando buffers alugados a um pool, como este:
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);
}
Ao utilizar esta abordagem, alugam-se buffers de um pool, o que significa que, na maioria dos casos, evita-se uma alocação. Além disso, como os buffers emprestados não são limpos por defeito, também pode poupar o tempo necessário para os preencher com zeros, o que lhe proporciona outra pequena melhoria de desempenho. No exemplo anterior, carregar 1.000 ficheiros faz com que o tamanho total da alocação passe de cerca de 1 MB para apenas 1.024 bytes. Na prática, alocas e reutilizas apenas um único buffer.
O código anterior tem dois problemas principais:
-
ArrayPool<T>pode devolver buffers que tenham um tamanho superior ao solicitado. Para contornar este problema, precisa de devolver uma tupla que também indique o tamanho real usado no seu buffer alugado. - Ao simplesmente devolver um array, deve ter muito cuidado para acompanhar corretamente a sua vida útil e devolvê-lo ao pool adequado. Podes contornar este problema usando
MemoryPool<T>em vez disso e devolvendo umaIMemoryOwner<T>instância, mas ainda tens 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, por ser uma interface, e pelo facto de ser sempre necessário obter primeiro uma instância deMemory<T>, e depois umSpan<T>.
Para resolver ambos os problemas, pode refatorar este 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 devolvida IMemoryOwner<byte> elimina o buffer subjacente e devolve-o ao pool quando o seu IDisposable.Dispose método é invocado. Podes usá-lo para obter uma instância de Memory<T> ou Span<T> para interagir com os dados carregados e depois descartá-la quando já não precisares. Além disso, todas as MemoryOwner<T> propriedades (como MemoryOwner<T>.Span) respeitam o tamanho inicial solicitado que usou, por isso já não precisa de controlar manualmente o tamanho efetivo dentro do buffer alugado.
Exemplos
Podes encontrar mais exemplos nos testes unitários.
.NET Community Toolkit