Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
La MemoryOwner<T> classe è un tipo di buffer che implementa IMemoryOwner<T>. Include una proprietà di lunghezza incorporata e una serie di API orientate alle prestazioni. Si tratta essenzialmente di un wrapper leggero per il tipo ArrayPool<T>, con alcune funzionalità di supporto aggiuntive.
API della piattaforma:
MemoryOwner<T>,AllocationMode
Funzionamento
MemoryOwner<T> presenta le funzionalità principali seguenti:
- Uno dei problemi principali relativi alle matrici restituite dalle
ArrayPool<T>API e alleIMemoryOwner<T>istanze restituite dalleMemoryPool<T>API è che le dimensioni specificate vengono usate solo come dimensioni minime : le dimensioni effettive dei buffer restituiti potrebbero essere maggiori.MemoryOwner<T>risolve questo problema archiviando anche le dimensioni richieste originali, quindiMemory<T>eSpan<T>le istanze recuperate da esso non devono mai essere sezionate manualmente. - Quando si usa
IMemoryOwner<T>, per ottenere unSpan<T>per il buffer sottostante, è necessario prima ottenere un'istanza diMemory<T>e poi un'istanza diSpan<T>. Questo processo è piuttosto costoso e spesso non necessario, perché potrebbe non essere necessario l'intermedioMemory<T>. Ha inveceMemoryOwner<T>una proprietà aggiuntivaSpanestremamente leggera perché esegue direttamente il wrapping della matrice internaT[]noleggiata dal pool. - Per impostazione predefinita, il pool non cancella i buffer che alloca. Se il pool non ha cancellato i buffer quando li ha restituiti in precedenza, potrebbero contenere dati spazzatura. In genere, è necessario cancellare manualmente questi buffer allocati, il che può risultare complesso, soprattutto se eseguito di frequente.
MemoryOwner<T>offre un approccio più flessibile tramite l'APIAllocate(int, AllocationMode). Questo metodo non solo alloca una nuova istanza della dimensione richiesta, ma consente anche di specificare la modalità di allocazione da usare: la stessa diArrayPool<T>o quella che cancella automaticamente il buffer noleggiato. - A volte è possibile noleggiare un buffer con dimensioni maggiori rispetto a quelle effettivamente necessarie e quindi ridimensionarlo. In genere, è necessario noleggiare un nuovo buffer e copiare l'area di interesse dal buffer precedente. Invece,
MemoryOwner<T>espone un'APISlice(int, int)che restituisce una nuova istanza che avvolge l'area di interesse specificata. Questo approccio ignora l'affitto di un nuovo buffer e la copia completa degli elementi.
Sintassi
Ecco un esempio di come noleggiare un buffer e recuperare un'istanza Memory<T> :
// 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;
}
In questo esempio si usa un using blocco per dichiarare il MemoryOwner<T> buffer. Questo approccio è particolarmente utile perché la matrice sottostante viene restituita automaticamente al pool alla fine del blocco. Se non si ha il controllo diretto sulla durata di un'istanza MemoryOwner<T> , il buffer viene restituito al pool quando il Garbage Collector finalizza l'oggetto. In entrambi i casi, i buffer noleggiati vengono sempre restituiti correttamente al pool condiviso.
Quando è consigliabile usare questo tipo?
È possibile usare MemoryOwner<T> come tipo di buffer per utilizzo generico. Riduce al minimo il numero di allocazioni nel tempo perché riutilizza internamente le stesse matrici da un pool condiviso. Un caso d'uso comune consiste nel sostituire new T[] le allocazioni di array, soprattutto quando si eseguono operazioni ripetute che richiedono un buffer temporaneo per funzionare o producono un buffer come risultato.
Si supponga di avere un set di dati costituito da una serie di file binari ed è necessario leggere tutti questi file ed elaborarli in qualche modo. Per separare correttamente il codice, è possibile scrivere un metodo che legge semplicemente un file binario, che potrebbe essere simile al seguente:
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;
}
Prendere nota dell'espressione new byte[] . Se si legge un numero elevato di file, si allocano molte nuove matrici, che comporta una notevole pressione sul Garbage Collector. È possibile effettuare il refactoring di questo codice usando buffer affittati da un pool, come in questo modo:
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 questo approccio, si noleggiano buffer da un pool, il che significa che nella maggior parte dei casi si evita un'allocazione. Inoltre, poiché i buffer noleggiati non vengono cancellati per impostazione predefinita, è possibile risparmiare anche il tempo necessario per riempirli con zeri, offrendo così un ulteriore piccolo incremento delle prestazioni. Nell'esempio precedente, il caricamento di 1.000 file porta le dimensioni totali di allocazione da circa 1 MB a soli 1.024 byte. Allocare e riutilizzare in modo efficace solo un singolo buffer.
Il codice precedente presenta due problemi principali:
-
ArrayPool<T>può restituire buffer con dimensioni maggiori delle dimensioni richieste. Per risolvere questo problema, è necessario restituire una tupla che indichi anche la dimensione effettivamente usata nel buffer affittato. - Restituendo semplicemente una matrice, è necessario prestare particolare attenzione a tracciare correttamente la sua durata e restituirla al pool appropriato. È possibile risolvere questo problema usando
MemoryPool<T>invece e restituendo un'istanzaIMemoryOwner<T>, ma si è comunque verificato un problema relativo ai buffer in affitto con dimensioni maggiori rispetto a quelle necessarie. Inoltre,IMemoryOwner<T>comporta un sovraccarico durante il recupero di unSpan<T>su cui lavorare, a causa del fatto che si tratta di un'interfaccia e del fatto che è sempre necessario ottenere prima un'istanzaMemory<T>e poi unSpan<T>.
Per risolvere entrambi questi problemi, è possibile effettuare di nuovo il refactoring di questo codice 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;
}
L'istanza restituita IMemoryOwner<byte> elimina il buffer sottostante e lo restituisce al pool quando viene richiamato il relativo IDisposable.Dispose metodo. È possibile usarlo per ottenere un'istanza Memory<T> o Span<T> per interagire con i dati caricati e quindi eliminare l'istanza quando non è più necessaria. Inoltre, tutte le MemoryOwner<T> proprietà (ad esempio MemoryOwner<T>.Span) rispettano le dimensioni iniziali richieste usate, quindi non è più necessario tenere traccia manualmente delle dimensioni effettive all'interno del buffer affittato.
Esempi
Altri esempi sono disponibili negli unit test.
.NET Community Toolkit