İngilizce dilinde oku

Aracılığıyla paylaş


.NET'te Arabelleklerle Çalışma

Bu makalede, birden çok arabellekte çalışan verilerin okunmasında yardımcı olan türlere genel bir bakış sağlanır. Bunlar öncelikli olarak nesneleri desteklemek PipeReader için kullanılır.

IBufferWriter<T>

System.Buffers.IBufferWriter<T> zaman uyumlu arabelleğe alınan yazma için yapılan bir sözleşmedir. En düşük düzeyde, arabirim:

  • Temeldir ve kullanımı zor değildir.
  • veya Memory<T>Span<T>öğesine erişime izin verir. Memory<T> veya Span<T> öğesine yazılabilir ve kaç T öğenin yazıldığını belirleyebilirsiniz.
void WriteHello(IBufferWriter<byte> writer)
{
    // Request at least 5 bytes.
    Span<byte> span = writer.GetSpan(5);
    ReadOnlySpan<char> helloSpan = "Hello".AsSpan();
    int written = Encoding.ASCII.GetBytes(helloSpan, span);

    // Tell the writer how many bytes were written.
    writer.Advance(written);
}

Yukarıdaki yöntem:

  • kullanarak 'den en az 5 baytlık bir arabellek istemektedir IBufferWriter<byte>GetSpan(5).
  • "Hello" ASCII dizesi için baytları döndürülen Span<byte>öğesine yazar.
  • Arabelleğe kaç bayt yazıldığını belirten çağrılar IBufferWriter<T> .

Bu yazma yöntemi tarafından sağlanan arabelleği kullanırMemory<T>Span<T>/.IBufferWriter<T> Alternatif olarak, Write uzantı yöntemi var olan bir arabelleği öğesine kopyalamak için IBufferWriter<T>kullanılabilir. Write arama GetSpan/Advance işini uygun şekilde yapar, bu nedenle yazdıktan sonra arama Advance yapmanıza gerek yoktur:

void WriteHello(IBufferWriter<byte> writer)
{
    byte[] helloBytes = Encoding.ASCII.GetBytes("Hello");

    // Write helloBytes to the writer. There's no need to call Advance here
    // since Write calls Advance.
    writer.Write(helloBytes);
}

ArrayBufferWriter<T> , yedekleme deposu tek bir bitişik dizi olan uygulamasının bir uygulamasıdır IBufferWriter<T> .

IBufferWriter yaygın sorunları

  • GetSpan ve GetMemory en az istenen bellek miktarına sahip bir arabellek döndürür. Tam arabellek boyutlarını varsaymayın.
  • Ardışık çağrıların aynı arabelleği veya aynı boyutlu arabelleği döndüreceğinin garantisi yoktur.
  • Daha fazla veri yazmaya devam etmek için çağrıldıktan Advance sonra yeni bir arabellek istenmelidir. Daha önce alınan arabelleğe çağrıldıktan sonra Advance yazılamaz.

ReadOnlySequence<T>

ReadOnlySequence showing memory in pipe and below that sequence position of read-only memory

ReadOnlySequence<T> , öğesinin bitişik veya bitişik olmayan dizisini temsil eden bir yapıdır T. Şu kaynaklardan oluşturulabilir:

  1. T[]
  2. ReadOnlyMemory<T>
  3. Dizinin başlangıç ve bitiş konumunu temsil eden bağlantılı liste düğümü ReadOnlySequenceSegment<T> ve dizini çifti.

Üçüncü gösterim, üzerinde çeşitli işlemler üzerinde performans etkilerine sahip olduğu için en ilginç temsildir ReadOnlySequence<T>:

Gösterimi İşlem Karmaşıklık
T[]/ReadOnlyMemory<T> Length O(1)
T[]/ReadOnlyMemory<T> GetPosition(long) O(1)
T[]/ReadOnlyMemory<T> Slice(int, int) O(1)
T[]/ReadOnlyMemory<T> Slice(SequencePosition, SequencePosition) O(1)
ReadOnlySequenceSegment<T> Length O(1)
ReadOnlySequenceSegment<T> GetPosition(long) O(number of segments)
ReadOnlySequenceSegment<T> Slice(int, int) O(number of segments)
ReadOnlySequenceSegment<T> Slice(SequencePosition, SequencePosition) O(1)

Bu karma gösterim nedeniyle dizinleri ReadOnlySequence<T> tamsayı yerine olarak SequencePosition kullanıma sunar. A SequencePosition:

  • Bir dizini ReadOnlySequence<T> kaynağında temsil eden opak bir değerdir.
  • Bir tamsayı ve bir nesne olmak üzere iki bölümden oluşur. Bu iki değerin temsili, uygulamasına ReadOnlySequence<T>bağlıdır.

Verilere erişme

, ReadOnlySequence<T> verileri bir numaralandırılabilir ReadOnlyMemory<T>olarak kullanıma sunar. Segmentlerin her birini listelemek temel bir foreach kullanılarak yapılabilir:

long FindIndexOf(in ReadOnlySequence<byte> buffer, byte data)
{
    long position = 0;

    foreach (ReadOnlyMemory<byte> segment in buffer)
    {
        ReadOnlySpan<byte> span = segment.Span;
        var index = span.IndexOf(data);
        if (index != -1)
        {
            return position + index;
        }

        position += span.Length;
    }

    return -1;
}

Yukarıdaki yöntem her segmentte belirli bir bayt arar. Her kesimin SequencePositionizlemeniz gerekiyorsa, ReadOnlySequence<T>.TryGet daha uygundur. Sonraki örnek, önceki kodu tamsayı yerine bir SequencePosition döndürecek şekilde değiştirir. SequencePosition döndüren, çağıranın verileri belirli bir dizine almak için ikinci bir taramadan kaçınmasına izin verme avantajına sahiptir.

SequencePosition? FindIndexOf(in ReadOnlySequence<byte> buffer, byte data)
{
    SequencePosition position = buffer.Start;
    SequencePosition result = position;

    while (buffer.TryGet(ref position, out ReadOnlyMemory<byte> segment))
    {
        ReadOnlySpan<byte> span = segment.Span;
        var index = span.IndexOf(data);
        if (index != -1)
        {
            return buffer.GetPosition(index, result);
        }

        result = position;
    }
    return null;
}

ve TryGet birleşimi SequencePosition bir numaralandırıcı gibi davranır. Konum alanı, her yinelemenin başında içindeki her segmentin ReadOnlySequence<T>başlangıcı olacak şekilde değiştirilir.

Yukarıdaki yöntem, üzerinde ReadOnlySequence<T>bir uzantı yöntemi olarak bulunur. PositionOf yukarıdaki kodu basitleştirmek için kullanılabilir:

SequencePosition? FindIndexOf(in ReadOnlySequence<byte> buffer, byte data) => buffer.PositionOf(data);

ReadOnlySequence<T İşleme>

Veriler dizi içindeki birden çok segmente bölünebileceğinden, bir ReadOnlySequence<T> işlenme zor olabilir. En iyi performans için kodu iki yola bölün:

  • Tek kesimli servis talebiyle ilgilenen hızlı bir yol.
  • Segmentler arasında bölünmüş verilerle ilgilenen yavaş bir yol.

Çok segmentli dizilerdeki verileri işlemek için kullanılabilecek birkaç yaklaşım vardır:

  • kullanın SequenceReader<T>.
  • Ayrıştırılan segment içindeki ve dizinini SequencePosition izleyerek veri segmentini segmente göre ayrıştırın. Bu, gereksiz ayırmaları önler, ancak özellikle küçük arabellekler için verimsiz olabilir.
  • değerini ReadOnlySequence<T> bitişik bir diziye kopyalayın ve tek bir arabellek gibi davranın:
    • boyutu ReadOnlySequence<T> küçükse, stackalloc işlecini kullanarak verileri yığın ayrılmış arabelleğe kopyalamak mantıklı olabilir.
    • kullanarak ArrayPool<T>.Sharedöğesini ReadOnlySequence<T> havuza alınan bir diziye kopyalayın.
    • ReadOnlySequence<T>.ToArray() adresini kullanın. Yığında yeni T[] bir ayırma yaptığı için sık erişimli yollarda bu önerilmez.

Aşağıdaki örneklerde işlemeye yönelik bazı yaygın durumlar gösterilmektedir ReadOnlySequence<byte>:

İkili verileri işleme

Aşağıdaki örnek, başından itibaren 4 baytlık büyük endian tamsayı uzunluğunu ayrıştırmaktadır ReadOnlySequence<byte>.

bool TryParseHeaderLength(ref ReadOnlySequence<byte> buffer, out int length)
{
    // If there's not enough space, the length can't be obtained.
    if (buffer.Length < 4)
    {
        length = 0;
        return false;
    }

    // Grab the first 4 bytes of the buffer.
    var lengthSlice = buffer.Slice(buffer.Start, 4);
    if (lengthSlice.IsSingleSegment)
    {
        // Fast path since it's a single segment.
        length = BinaryPrimitives.ReadInt32BigEndian(lengthSlice.First.Span);
    }
    else
    {
        // There are 4 bytes split across multiple segments. Since it's so small, it
        // can be copied to a stack allocated buffer. This avoids a heap allocation.
        Span<byte> stackBuffer = stackalloc byte[4];
        lengthSlice.CopyTo(stackBuffer);
        length = BinaryPrimitives.ReadInt32BigEndian(stackBuffer);
    }

    // Move the buffer 4 bytes ahead.
    buffer = buffer.Slice(lengthSlice.End);

    return true;
}
Metin verilerini işleme

Aşağıdaki örnek:

  • içindeki ReadOnlySequence<byte> ilk yeni satırı (\r\n) bulur ve out 'line' parametresi aracılığıyla döndürür.
  • Giriş arabelleğinin dışında tutarak \r\n bu çizgiyi kırpılır.
static bool TryParseLine(ref ReadOnlySequence<byte> buffer, out ReadOnlySequence<byte> line)
{
    SequencePosition position = buffer.Start;
    SequencePosition previous = position;
    var index = -1;
    line = default;

    while (buffer.TryGet(ref position, out ReadOnlyMemory<byte> segment))
    {
        ReadOnlySpan<byte> span = segment.Span;

        // Look for \r in the current segment.
        index = span.IndexOf((byte)'\r');

        if (index != -1)
        {
            // Check next segment for \n.
            if (index + 1 >= span.Length)
            {
                var next = position;
                if (!buffer.TryGet(ref next, out ReadOnlyMemory<byte> nextSegment))
                {
                    // You're at the end of the sequence.
                    return false;
                }
                else if (nextSegment.Span[0] == (byte)'\n')
                {
                    //  A match was found.
                    break;
                }
            }
            // Check the current segment of \n.
            else if (span[index + 1] == (byte)'\n')
            {
                // It was found.
                break;
            }
        }

        previous = position;
    }

    if (index != -1)
    {
        // Get the position just before the \r\n.
        var delimeter = buffer.GetPosition(index, previous);

        // Slice the line (excluding \r\n).
        line = buffer.Slice(buffer.Start, delimeter);

        // Slice the buffer to get the remaining data after the line.
        buffer = buffer.Slice(buffer.GetPosition(2, delimeter));
        return true;
    }

    return false;
}
Boş segmentler

Boş segmentleri içinde ReadOnlySequence<T>depolamak geçerlidir. Kesimler açıkça numaralandırılıyorken boş segmentler oluşabilir:

static void EmptySegments()
{
    // This logic creates a ReadOnlySequence<byte> with 4 segments,
    // two of which are empty.
    var first = new BufferSegment(new byte[0]);
    var last = first.Append(new byte[] { 97 })
                    .Append(new byte[0]).Append(new byte[] { 98 });

    // Construct the ReadOnlySequence<byte> from the linked list segments.
    var data = new ReadOnlySequence<byte>(first, 0, last, 1);

    // Slice using numbers.
    var sequence1 = data.Slice(0, 2);

    // Slice using SequencePosition pointing at the empty segment.
    var sequence2 = data.Slice(data.Start, 2);

    Console.WriteLine($"sequence1.Length={sequence1.Length}"); // sequence1.Length=2
    Console.WriteLine($"sequence2.Length={sequence2.Length}"); // sequence2.Length=2

    // sequence1.FirstSpan.Length=1
    Console.WriteLine($"sequence1.FirstSpan.Length={sequence1.FirstSpan.Length}");

    // Slicing using SequencePosition will Slice the ReadOnlySequence<byte> directly
    // on the empty segment!
    // sequence2.FirstSpan.Length=0
    Console.WriteLine($"sequence2.FirstSpan.Length={sequence2.FirstSpan.Length}");

    // The following code prints 0, 1, 0, 1.
    SequencePosition position = data.Start;
    while (data.TryGet(ref position, out ReadOnlyMemory<byte> memory))
    {
        Console.WriteLine(memory.Length);
    }
}

class BufferSegment : ReadOnlySequenceSegment<byte>
{
    public BufferSegment(Memory<byte> memory)
    {
        Memory = memory;
    }

    public BufferSegment Append(Memory<byte> memory)
    {
        var segment = new BufferSegment(memory)
        {
            RunningIndex = RunningIndex + Memory.Length
        };
        Next = segment;
        return segment;
    }
}

Yukarıdaki kod, boş kesimler içeren bir ReadOnlySequence<byte> oluşturur ve bu boş segmentlerin çeşitli API'leri nasıl etkilediğini gösterir:

  • ReadOnlySequence<T>.Slice boş bir SequencePosition kesime işaret eden bir kesim ile bu segment korunur.
  • ReadOnlySequence<T>.Slice int ile boş kesimleri atlar.
  • numaralandırılıyorsa ReadOnlySequence<T> boş kesimler numaralandırılıyor.

ReadOnlySequence<T> ve SequencePosition ile ilgili olası sorunlar

Normal ile normal karşıtlık yaparken ReadOnlySequence<T>SequencePosition///intReadOnlySpan<T>ReadOnlyMemory<T>/T[]birkaç olağan dışı sonuç vardır:

  • SequencePosition , mutlak bir konum için değil, belirli ReadOnlySequence<T>bir konum işaretçisidir. Belirli ReadOnlySequence<T>bir öğesine göre olduğundan, kaynağın dışında ReadOnlySequence<T> kullanıldığında bir anlamı yoktur.
  • Aritmetik, olmadan ReadOnlySequence<T>üzerinde SequencePosition gerçekleştirilemez. Bu, gibi position++ temel şeyler yapmanın yazıldığını position = ReadOnlySequence<T>.GetPosition(1, position)gösterir.
  • GetPosition(long)negatif dizinleri desteklemez. Bu, tüm segmentlerde yürümeden ikinci karakteri son karaktere getirmek mümkün olmadığı anlamına gelir.
  • İkisi SequencePosition karşılaştırılamaz ve bu da şunları zorlaştırıyor:
    • Bir konumun başka bir konumdan büyük veya daha küçük olup olmadığını bilme.
    • Bazı ayrıştırma algoritmaları yazın.
  • ReadOnlySequence<T>bir nesne başvurusundan daha büyüktür ve mümkün olduğunca içinde veya başvurusu tarafından geçirilmelidir. ReadOnlySequence<T> Geçiş veya inref yapının kopyalarını azaltır.
  • Boş segmentler:
    • içinde ReadOnlySequence<T>geçerlidir.
    • yöntemi kullanılarak ReadOnlySequence<T>.TryGet yinelenirken görüntülenebilir.
    • Yöntemi nesnelerle SequencePosition kullanarak sırayı ReadOnlySequence<T>.Slice() dilimleyerek görüntülenebilir.

SequenceReader<T>

SequenceReader<T>:

  • bir işlemini basitleştirmek için .NET Core 3.0'da kullanıma sunulan yeni bir ReadOnlySequence<T>türdür.
  • Tek bir kesim ile çok segmentli ReadOnlySequence<T>ReadOnlySequence<T>arasındaki farkları bir arada sunar.
  • Bölümler arasında bölünebilen veya bölünemeyebilir ikili verileri ve metin verilerini (byte ve char) okumak için yardımcılar sağlar.

hem ikili hem de sınırlandırılmış verileri işlemek için yerleşik yöntemler vardır. Aşağıdaki bölümde, aynı yöntemlerin ile nasıl göründüğü gösterilmektedir SequenceReader<T>:

Verilere erişme

SequenceReader<T> doğrudan içinde verileri listelemek için yöntemlere ReadOnlySequence<T> sahiptir. Aşağıdaki kod, bir'i bir kerede işleme ReadOnlySequence<byte>byte örneğidir:

while (reader.TryRead(out byte b))
{
    Process(b);
}

, CurrentSpan yöntemde el ile yapılana benzer şekilde geçerli kesimin Spanöğesini kullanıma sunar.

Konum kullan

Aşağıdaki kod, uygulamasının kullanımına yönelik FindIndexOf örnek bir uygulamadır SequenceReader<T>:

SequencePosition? FindIndexOf(in ReadOnlySequence<byte> buffer, byte data)
{
    var reader = new SequenceReader<byte>(buffer);

    while (!reader.End)
    {
        // Search for the byte in the current span.
        var index = reader.CurrentSpan.IndexOf(data);
        if (index != -1)
        {
            // It was found, so advance to the position.
            reader.Advance(index);

            return reader.Position;
        }
        // Skip the current segment since there's nothing in it.
        reader.Advance(reader.CurrentSpan.Length);
    }

    return null;
}

İkili verileri işleme

Aşağıdaki örnek, başından itibaren 4 baytlık büyük endian tamsayı uzunluğunu ayrıştırmaktadır ReadOnlySequence<byte>.

bool TryParseHeaderLength(ref ReadOnlySequence<byte> buffer, out int length)
{
    var reader = new SequenceReader<byte>(buffer);
    return reader.TryReadBigEndian(out length);
}

Metin verilerini işleme

static ReadOnlySpan<byte> NewLine => new byte[] { (byte)'\r', (byte)'\n' };

static bool TryParseLine(ref ReadOnlySequence<byte> buffer,
                         out ReadOnlySequence<byte> line)
{
    var reader = new SequenceReader<byte>(buffer);

    if (reader.TryReadTo(out line, NewLine))
    {
        buffer = buffer.Slice(reader.Position);

        return true;
    }

    line = default;
    return false;
}

SequenceReader<T> yaygın sorunları

  • SequenceReader<T> Değiştirilebilir bir yapı olduğundan, her zaman başvuru ile geçirilmelidir.
  • SequenceReader<T> bir başvuru yapısıdır , bu nedenle yalnızca zaman uyumlu yöntemlerde kullanılabilir ve alanlarda depolanamaz. Daha fazla bilgi için bkz . Ayırmalardan kaçınma.
  • SequenceReader<T> , yalnızca ileriye doğru okuyucu olarak kullanılmak üzere en iyi duruma getirilmiştir. Rewind, ReadPeekve IsNext API'leri kullanılarak ele alınamaz küçük yedeklemeler için tasarlanmıştır.