Aracılığıyla paylaş


MemoryOwner<T>

MemoryOwner<T>, ekli uzunluk özelliği ve performans odaklı API'ler dizisi uygulayan IMemoryOwner<T>bir arabellek türüdür. Bu temelde bazı ek yardımcı yardımcı yardımcı programları ile türü etrafında ArrayPool<T> hafif bir sarmalayıcıdır.

Platform API'leri: MemoryOwner<T>, AllocationMode

Nasıl çalışır?

MemoryOwner<T> aşağıdaki ana özelliklere sahiptir:

  • API'ler tarafından ArrayPool<T> döndürülen dizilerin ve API'ler tarafından MemoryPool<T> döndürülen örneklerin IMemoryOwner<T> temel sorunlarından biri, kullanıcı tarafından belirtilen boyutun yalnızca en düşük boyut olarak kullanılmasıdır: döndürülen arabelleklerin gerçek boyutu aslında daha büyük olabilir. MemoryOwner<T>bu sorunu çözmek için istenen özgün boyutu depolar, Span<T> böylece Memory<T> ve ondan alınan örneklerin asla el ile dilimlenmiş olması gerekmez.
  • kullanırken IMemoryOwner<T>, temel alınan arabellek için bir Span<T> almak için önce bir örnek, sonra da bir Memory<T> Span<T>gerekir. Bu oldukça pahalıdır ve genellikle gereksizdir, ara ara aslında Memory<T> hiç gerekmeyebilir. MemoryOwner<T>bunun yerine, havuzdan kiralanan iç T[] diziyi doğrudan sarmaladığı için son derece hafif olan ek Span bir özelliği vardır.
  • Havuzdan kiralanan arabellekler varsayılan olarak temizlenmez, bu da havuza daha önce geri döndürülürken temizlenmediyse, çöp verileri içerebileceği anlamına gelir. Normalde, kullanıcıların bu kiralanan arabellekleri el ile temizlemeleri gerekir ve bu da özellikle sık yapıldığında ayrıntılı olabilir. MemoryOwner<T> API aracılığıyla bu konuda daha esnek bir yaklaşıma Allocate(int, AllocationMode) sahiptir. Bu yöntem yalnızca tam olarak istenen boyutun yeni bir örneğini ayırmakla kalmaz, aynı zamanda hangi ayırma modunun kullanılacağını belirtmek için de kullanılabilir: ile aynı ArrayPool<T>olan veya kiralanan arabelleği otomatik olarak temizleyen bir örnek.
  • Arabelleklerin gerçekten gerekenden daha büyük bir boyutta kiralanıp daha sonra yeniden boyutlandırılma durumları vardır. Bu normalde kullanıcıların yeni bir arabellek kiralamasını ve ilgilendiği bölgeyi eski arabellekten kopyalamasını gerektirir. Bunun yerine, MemoryOwner<T> yalnızca belirtilen ilgi alanını sarmalayan yeni bir örnek döndüren bir API'yi kullanıma sunar Slice(int, int) . Bu, yeni bir arabellek kiralamayı ve öğeleri tamamen kopyalamayı atlamayı sağlar.

Sözdizimi

Arabellek kiralama ve örnek alma işlemine bir örnek aşağıda verilmiştir 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;
}

Bu örnekte, arabelleği bildirmek MemoryOwner<T> için bir using blok kullandık: Temel dizi bloğun sonundaki havuza otomatik olarak döndürüleceği için bu özellikle yararlıdır. Bunun yerine bir MemoryOwner<T> örneğin ömrü üzerinde doğrudan denetimimiz yoksa, nesne çöp toplayıcı tarafından sonlandırıldığında arabellek havuza döndürülür. Her iki durumda da, kiralanan arabellekler her zaman paylaşılan havuza doğru şekilde döndürülür.

Bu ne zaman kullanılmalıdır?

MemoryOwner<T> , paylaşılan bir havuzdan aynı dizileri dahili olarak yeniden kullandığından, zaman içinde yapılan ayırma sayısını en aza indirme avantajına sahip genel amaçlı bir arabellek türü olarak kullanılabilir. Yaygın bir kullanım örneği, özellikle üzerinde çalışmak için geçici bir arabelleğe ihtiyaç duyan veya sonuç olarak arabellek oluşturan yinelenen işlemler yaparken dizi ayırmalarını değiştirmektir new T[] .

Bir dizi ikili dosyadan oluşan bir veri kümemiz olduğunu ve tüm bu dosyaları okumamız ve bunları bir şekilde işlememiz gerektiğini varsayalım. Kodumuzu düzgün bir şekilde ayırmak için tek bir ikili dosyayı okuyan bir yöntem yazabiliriz. Bu yöntem şöyle görünebilir:

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

Bu ifadeye new byte[] dikkat edin. Çok sayıda dosya okursak, çok sayıda yeni dizi ayıracağız ve bu da çöp toplayıcı üzerinde büyük baskı oluşturacak. Bu kodu bir havuzdan kiralanan arabellekleri kullanarak yeniden düzenlemek isteyebiliriz, örneğin:

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

Bu yaklaşımı kullanarak arabellekler artık bir havuzdan kiralanır ve bu da çoğu durumda ayırmayı atlayabildiğimiz anlamına gelir. Ayrıca, kiralanan arabellekler varsayılan olarak temizlenmediğinden, bunları sıfırlarla doldurmak için gereken zamandan da tasarruf edebiliriz, bu da bize küçük bir performans iyileştirmesi daha sağlar. Yukarıdaki örnekte, 1000 dosya yüklenirse toplam ayırma boyutu yaklaşık 1 MB'tan yalnızca 1024 bayta indirilebilir. Yalnızca tek bir arabellek etkili bir şekilde ayrılır ve otomatik olarak yeniden kullanılır.

Yukarıdaki kodla ilgili iki ana sorun vardır:

  • ArrayPool<T> , istenenden daha büyük bir boyuta sahip arabellekler döndürebilir. Bu sorunu geçici olarak çözmek için, kiralanan arabellekte kullanılan gerçek boyutu da gösteren bir tanımlama grubu döndürmemiz gerekir.
  • Yalnızca bir dizi döndürerek, ömrünü düzgün bir şekilde izlemek ve uygun havuza döndürmek için çok dikkatli olmamız gerekir. Bunun yerine kullanarak MemoryPool<T> ve bir örneği döndürerek bu sorunu geçici olarak IMemoryOwner<T> çözebiliriz, ancak yine de kiralanmış arabelleklerin ihtiyacımız olandan daha büyük bir boyuta sahip olması sorunuyla karşılaşıyoruz. Ayrıca, IMemoryOwner<T> bir arabirim olması ve her zaman önce bir örnek, sonra Span<T>da bir Memory<T> almamız gerektiği için üzerinde çalışılması gereken bir alınırken Span<T> bazı ek yükleri vardır.

Bu iki sorunu da çözmek için kullanarak MemoryOwner<T>bu kodu yeniden düzenleyebiliriz:

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

Döndürülen IMemoryOwner<byte> örnek, yöntemi çağrıldığında IDisposable.Dispose temel alınan arabelleği yok etme ve havuza döndürme ile ilgilenir. Yüklenen verilerle etkileşime geçmek için bir Memory<T> veya Span<T> örneği almak ve artık ihtiyaç duymadığımızda örneği atmak için kullanabiliriz. Buna ek olarak, tüm MemoryOwner<T> özellikler (gibi MemoryOwner<T>.Span) kullandığımız ilk istenen boyuta göredir, bu nedenle artık kiralanan arabellek içindeki etkin boyutu el ile izlememiz gerekmez.

Örnekler

Birim testlerinde daha fazla örnek bulabilirsiniz.