MemoryOwner<T> kelas

Kelas MemoryOwner<T> adalah jenis buffer yang mengimplementasikan IMemoryOwner<T>. Ini termasuk properti panjang yang disematkan dan serangkaian API berorientasi performa. Ini pada dasarnya adalah pembungkus ringan di sekitar jenis ArrayPool<T>, dengan beberapa utilitas pembantu tambahan.

API Platform:MemoryOwner<T>, AllocationMode

Cara kerjanya

MemoryOwner<T> memiliki fitur utama berikut:

  • Salah satu masalah utama dengan array yang dikembalikan oleh ArrayPool<T> API dan dengan IMemoryOwner<T> instans yang dikembalikan oleh MemoryPool<T> API adalah ukuran yang Anda tentukan hanya digunakan sebagai ukuran minimum : ukuran aktual buffer yang dikembalikan mungkin lebih besar. MemoryOwner<T> memecahkan masalah ini dengan juga menyimpan ukuran asli yang diminta, sehingga Memory<T> dan Span<T> instans yang diambil darinya tidak perlu diiris secara manual.
  • Ketika Anda menggunakan IMemoryOwner<T>, untuk memperoleh Span<T> bagi buffer yang mendasar, Anda harus terlebih dahulu mendapatkan instance Memory<T>, dan kemudian Span<T>. Proses ini cukup mahal dan seringkali tidak perlu, karena Anda mungkin tidak memerlukan perantara Memory<T>. Sebaliknya, MemoryOwner<T> memiliki properti tambahan Span yang sangat ringan karena langsung membungkus array internal T[] yang disewa dari kumpulan.
  • Pool tidak menghapus buffer yang disewanya secara default. Jika kumpulan tidak menghapus buffer saat sebelumnya mengembalikannya, mereka mungkin berisi data sampah. Biasanya, Anda perlu membersihkan buffer sewaan ini secara manual, yang bisa terasa membosankan, terutama ketika dilakukan secara sering. MemoryOwner<T> menawarkan pendekatan yang lebih fleksibel melalui Allocate(int, AllocationMode) API. Metode ini tidak hanya mengalokasikan instans baru dengan ukuran yang diminta, tetapi juga memungkinkan Anda menentukan mode alokasi mana yang akan digunakan: baik yang sama dengan ArrayPool<T>, atau yang secara otomatis menghapus buffer yang disewa.
  • Terkadang Anda mungkin menyewa buffer dengan ukuran yang lebih besar dari apa yang sebenarnya Anda butuhkan, dan kemudian mengubah ukurannya. Biasanya, Anda harus menyewa buffer baru dan menyalin wilayah yang menarik dari buffer lama. Sebagai gantinya, MemoryOwner<T> mengekspos Slice(int, int) API yang mengembalikan instance baru yang membungkus area tertentu yang diminati. Pendekatan ini mengabaikan penyewaan buffer baru dan menduplikasi item sepenuhnya.

Sintaks

Berikut adalah contoh cara menyewa buffer dan mengambil Memory<T> instans:

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

Dalam contoh ini, Anda menggunakan using blok untuk mendeklarasikan MemoryOwner<T> buffer. Pendekatan ini sangat berguna karena array yang mendasarinya secara otomatis dikembalikan ke himpunan di akhir blok. Jika Anda tidak memiliki kontrol langsung atas masa hidup instans MemoryOwner<T>, buffer akan dikembalikan ke kumpulan ketika pengumpul sampah memfinalisasi objek. Dalam kedua kasus, buffer yang dipinjam selalu dikembalikan dengan benar ke kumpulan bersama.

Kapan Anda harus menggunakan jenis ini?

Anda dapat menggunakan MemoryOwner<T> sebagai jenis buffer tujuan umum. Ini meminimalkan jumlah alokasi dari waktu ke waktu karena secara internal menggunakan kembali array yang sama dari kumpulan bersama. Penggunaan umum adalah mengganti alokasi memori new T[] array, terutama ketika Anda melakukan operasi berulang yang memerlukan buffer sementara untuk pengolahan atau menghasilkan buffer sebagai hasilnya.

Misalkan Anda memiliki himpunan data yang terdiri dari serangkaian file biner, dan Anda perlu membaca semua file ini dan memprosesnya dalam beberapa cara. Untuk memisahkan kode dengan benar, Anda mungkin menulis metode yang hanya membaca satu file biner, yang mungkin terlihat seperti ini:

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

Perhatikan ekspresi new byte[]. Jika Anda membaca sejumlah besar file, Anda mengalokasikan banyak array baru, yang memberikan banyak tekanan pada pengumpul sampah. Anda mungkin ingin merefaktor kode ini dengan menggunakan buffer yang disewa dari kumpulan, seperti:

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

Dengan menggunakan pendekatan ini, Anda menyewa buffer dari kumpulan, yang berarti bahwa dalam banyak kasus Anda dapat menghindari alokasi. Selain itu, karena buffer yang dipinjam tidak dikosongkan secara default, kamu juga dapat menghemat waktu yang diperlukan untuk mengisinya dengan nol. Ini memberikan peningkatan kinerja kecil lainnya. Dalam contoh sebelumnya, memuat 1.000 file membawa ukuran alokasi total dari sekitar 1 MB menjadi hanya 1.024 byte. Anda secara efektif mengalokasikan dan menggunakan kembali hanya satu buffer.

Kode sebelumnya memiliki dua masalah utama:

  • ArrayPool<T> mungkin mengembalikan buffer yang memiliki ukuran lebih besar dari ukuran yang diminta. Untuk mengatasi masalah ini, Anda perlu mengembalikan tuple yang juga menunjukkan ukuran aktual yang digunakan dalam buffer sewaan Anda.
  • Dengan hanya mengembalikan array, Anda harus ekstra hati-hati untuk melacak masa pakainya dengan benar dan mengembalikannya ke kumpulan yang sesuai. Anda mungkin bisa mengatasi masalah ini dengan menggunakan MemoryPool<T> sebagai gantinya dan dengan mengembalikan sebuah instance IMemoryOwner<T>, tetapi Anda masih memiliki masalah buffer yang disewakan yang memiliki ukuran lebih besar dari yang Anda butuhkan. Selain itu, IMemoryOwner<T> memiliki beberapa overhead saat mengambil Span<T> untuk dikerjakan, karena itu adalah sebuah antarmuka, dan juga fakta bahwa Anda selalu perlu mendapatkan Memory<T> instance terlebih dahulu, dan kemudian Span<T>.

Untuk mengatasi kedua masalah ini, Anda dapat merefaktor kode ini lagi dengan menggunakan 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;
}

Instans yang dikembalikan IMemoryOwner<byte> meniadakan buffer yang mendasar dan mengembalikannya ke kumpulan sumber daya saat metodenya IDisposable.Dispose dipanggil. Anda dapat menggunakannya untuk mendapatkan Memory<T> instance atau Span<T> untuk berinteraksi dengan data yang dimuat, lalu melepaskan instance saat Anda tidak lagi membutuhkannya. Selain itu, semua MemoryOwner<T> properti (seperti MemoryOwner<T>.Span) menghormati ukuran awal yang Anda minta, sehingga Anda tidak perlu lagi secara manual melacak ukuran efektif dalam buffer yang disewa.

Contoh

Anda dapat menemukan lebih banyak contoh dalam pengujian unit.