MemoryOwner<T> – třída

Třída MemoryOwner<T> je typ vyrovnávací paměti (buffer), který implementuje IMemoryOwner<T>. Zahrnuje vloženou vlastnost délky a řadu rozhraní API orientovaných na výkon. Je to v podstatě lehká obálka kolem ArrayPool<T> typu, s některými dalšími pomocnými užitečnými funkcemi.

Rozhraní API platformy:MemoryOwner<T>, AllocationMode

Jak to funguje

MemoryOwner<T> má následující hlavní funkce:

  • Jedním z hlavních problémů s poli vrácenými rozhraními ArrayPool<T> API a instancemi vrácenými rozhraními IMemoryOwner<T> API je, že zadaná velikost se používá pouze jako MemoryPool<T> velikost: skutečná velikost vrácených vyrovnávacích pamětí může být větší. MemoryOwner<T> tento problém řeší také uložením původní požadované velikosti, takže Memory<T> instance Span<T> načtené z ní nemusí být nikdy ručně řezovány.
  • Při použití IMemoryOwner<T> nejprve získáte instanci Span<T>, a teprve poté je nutné získat Memory<T> pro základní vyrovnávací paměť. Tento proces je poměrně nákladný a často zbytečný, protože možná nebudete potřebovat mezistupeň Memory<T>. Místo toho má MemoryOwner<T> další Span vlastnost, která je extrémně účinná, jelikož přímo obaluje interní T[] pole pronajaté z fondu.
  • Fond nevymaže vyrovnávací paměti, které si ve výchozím nastavení pronajímá. Pokud fond nevymazal vyrovnávací paměti, když je předtím vrátil, mohou obsahovat nesmyslná data. Za normálních okolností je nutné tyto půjčené vyrovnávací paměti vymazat ručně, což může být zdlouhavé, zejména pokud se provádí často. MemoryOwner<T> nabízí flexibilnější přístup prostřednictvím Allocate(int, AllocationMode) rozhraní API. Tato metoda nejen přiděluje novou instanci přesně požadované velikosti, ale také umožňuje určit, který režim přidělení se má použít: buď stejný jako ArrayPool<T>, nebo takový, který automaticky vymaže pronajatou vyrovnávací paměť.
  • Někdy si můžete pronajmout vyrovnávací paměť s větší velikostí, než jakou skutečně potřebujete, a pak změnit její velikost. Za normálních okolností byste si museli pronajmout novou vyrovnávací paměť a zkopírovat oblast zájmu ze staré vyrovnávací paměti. Místo toho MemoryOwner<T> poskytuje Slice(int, int) rozhraní API, které vrací novou instanci zabalující zadanou oblast zájmu. Tento přístup přeskočí zapůjčení nové vyrovnávací paměti a úplné kopírování položek.

Syntaxe

Tady je příklad, jak si pronajmout vyrovnávací paměť a načíst Memory<T> instanci:

// 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;
}

V tomto příkladu použijete blok using k deklaraci vyrovnávací paměti MemoryOwner<T>. Tento přístup je zvlášť užitečný, protože základní pole se automaticky vrátí do fondu na konci bloku. Pokud nemáte přímou kontrolu nad životností MemoryOwner<T> instance, vyrovnávací paměť se vrátí do fondu, když objekt dokončí uvolňování paměti. V obou případech se půjčené vyrovnávací paměti vždy správně vrátí do sdíleného fondu.

Kdy byste měli použít tento typ?

Můžete použít MemoryOwner<T> jako typ vyrovnávací paměti pro obecné účely. Minimalizuje počet přidělení v průběhu času, protože interně znovu používá stejná pole ze sdíleného fondu. Běžným případem použití je nahrazení new T[] přidělení polí, zejména pokud provádíte opakované operace, které buď vyžadují dočasnou vyrovnávací paměť pro práci, nebo mají za následek vytvoření vyrovnávací paměti.

Předpokládejme, že máte datovou sadu skládající se z řady binárních souborů a potřebujete číst všechny tyto soubory a zpracovávat je nějakým způsobem. Pokud chcete kód správně oddělit, můžete napsat metodu, která jednoduše čte jeden binární soubor, který může vypadat takto:

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;
}

Všimněte si výrazu new byte[] . Pokud čtete velký počet souborů, přidělíte mnoho nových polí, což klade velký tlak na garbage collector. Tento kód můžete chtít refaktorovat pomocí vyrovnávacích pamětí půjčených z fondu, například:

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);
}

Pomocí tohoto přístupu si pronajímáte vyrovnávací paměti z fondu, což znamená, že ve většině případů se vyhnete přidělování paměti. Protože pronájem vyrovnávacích pamětí není ve výchozím nastavení vymazán, můžete také ušetřit čas potřebný k jejich naplnění nulami, což vám poskytne další malé zlepšení výkonu. Načtení 1 000 souborů v předchozím příkladu přináší celkovou velikost přidělení přibližně od 1 MB dolů na pouhých 1 024 bajtů. Efektivně přidělíte a znovu použijete pouze jednu vyrovnávací paměť.

Předchozí kód má dva hlavní problémy:

  • ArrayPool<T> může vrátit vyrovnávací paměti, které mají větší velikost, než je požadovaná. Chcete-li tento problém vyřešit, musíte vrátit n-tici, která také udává skutečnou použitou velikost ve vaší pronajaté vyrovnávací paměti.
  • Jednoduše vrácením pole musíte být opatrní, abyste správně sledovali jeho životnost a vrátili ho do příslušného fondu. Tento problém můžete obejít tím, že místo toho použijete MemoryPool<T> a vrátíte instanci IMemoryOwner<T>, ale stále máte problém s tím, že pronajaté vyrovnávací paměti mají větší velikost, než potřebujete. Kromě toho má určité režijní náklady při načítání IMemoryOwner<T> ke zpracování, protože se jedná o rozhraní, a skutečnost, že vždy nejprve potřebujete získat instanci Span<T>, a potom Memory<T>.

Pokud chcete vyřešit oba tyto problémy, můžete tento kód znovu refaktorovat pomocí 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;
}

Vrácená IMemoryOwner<byte> instance uvolní základní vyrovnávací paměť a vrátí ji do fondu při vyvolání její IDisposable.Dispose metody. Můžete ji použít k získání Memory<T> instance nebo Span<T> k interakci s načtenými daty a následnému odstranění instance, když ji už nepotřebujete. Kromě toho všechny MemoryOwner<T> vlastnosti (například MemoryOwner<T>.Span) respektují vámi požadovanou počáteční velikost, takže už nemusíte ručně sledovat efektivní velikost v pronajaté paměti.

Příklady

Další příklady najdete v jednotkových testech.