针对“缓冲区”编程是一个非常注重“性能”的地方,我们应该尽可能地避免武断地创建字节数组来存储读取的内容,这样不但会导致大量的字节拷贝,临时创建的字节数组还会带来GC压力。要正确、高效地读写缓冲内容,我们应该对几个我们可能熟悉的类型具有更深的认识。
一、Array、ArraySegment、Span
、Memory 与String
二、MemoryManager
三、ReadOnlySequence
四、创建“多段式”ReadOnlySequence
五、高效读取ReadOnlySequence
一、Array、ArraySegment、Span、Memory与String
Array、ArraySegment、Span
顾名思义,ArraySegment代表一个Array的“切片”,它利用如下所示的三个字段(_array、_offset和count)引用数组的一段连续的元素。由于Array是托管对象,所以ArraySegment映射的自然也只能是一段连续的托管内存。由于它是只读结构体(值类型),对GC无压力,在作为方法参数时按照“拷贝”传递。
public readonly struct ArraySegment{ private readonly T[] _array; private readonly int _offset; private readonly int _count; public T[]? Array => _array; public int Offset => _offset; public int Count => _count; }
不同于ArraySegment,一个Span
public readonly ref struct Span{ public Span(T[]? array); public Span(T[]? array, int start, int length); public unsafe Span(void* pointer, int length); public Span(ref T reference); internal Span(ref T reference, int length); }
由于Span
由于Memory
public readonly struct Memory{ public Memory(T[]? array);
internal Memory(T[] array, int start); public Memory(T[]? array, int start, int length); internal Memory(MemoryManagermanager, int length); internal Memory(MemoryManager manager, int start, int length); }
Span
二、MemoryManager
从上面给出的Memory
MemoryManager
public interface IMemoryOwner: IDisposable { Memory Memory { get; } }
托管对象可以以内存地址的形式进行操作,但前提是托管对象在内存中的地址不会改变,但是我们知道GC在进行压缩的时候是会对托管对象进行移动,所以我们需要固定托管内存的地址。MemoryManager
public interface IPinnable { MemoryHandle Pin(int elementIndex); void Unpin(); } public struct MemoryHandle : IDisposable { private unsafe void* _pointer; private GCHandle _handle; private IPinnable _pinnable; [CLSCompliant(false)] public unsafe void* Pointer => _pointer; [CLSCompliant(false)] public unsafe MemoryHandle(void* pointer, GCHandle handle = default(GCHandle), IPinnable? pinnable = null) { _pointer = pointer; _handle = handle; _pinnable = pinnable; } public unsafe void Dispose() { if (_handle.IsAllocated) { _handle.Free(); } if (_pinnable != null) { _pinnable.Unpin(); _pinnable = null; } _pointer = null; } }
抽象类MemoryManager
public abstract class MemoryManager: IMemoryOwner , IPinnable { public virtual Memory Memory => new(this, GetSpan().Length); public abstract Span GetSpan(); public abstract MemoryHandle Pin(int elementIndex = 0); public abstract void Unpin(); protected Memory CreateMemory(int length) => new(this, length); protected Memory CreateMemory(int start, int length)=> new(this, start, length); protected internal virtual bool TryGetArray(out ArraySegment segment) { segment = default; return false; } void IDisposable.Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } protected abstract void Dispose(bool disposing); }
如果我们需要创建了针对非托管内存的Memory
public sealed unsafe class UnmanagedMemoryManager: MemoryManager where T : unmanaged { private readonly T* _pointer; private readonly int _length; private MemoryHandle? _handle; public UnmanagedMemoryManager(T* pointer, int length) { _pointer = pointer; _length = length; } public override Span GetSpan() => new(_pointer, _length); public override MemoryHandle Pin(int elementIndex = 0)=> _handle ??= new (_pointer + elementIndex); public override void Unpin() => _handle?.Dispose(); protected override void Dispose(bool disposing) { } }
三、ReadOnlySequence
ReadOnlySequence
ReadOnlySequenceSegment
public abstract class ReadOnlySequenceSegment{ public ReadOnlyMemory Memory { get; protected set; } public ReadOnlySequenceSegment ? Next { get; protected set; } public long RunningIndex { get; protected set; } }
结构体SequencePosition定义如下,它表示ReadOnlySequence
public readonly struct SequencePosition { public object? GetObject(); public int GetInteger(); public SequencePosition(object? @object, int integer); }
ReadOnlySequence
public readonly struct ReadOnlySequence
{ public long Length { get; } public bool IsEmpty { get; } public bool IsSingleSegment { get; } public ReadOnlyMemory First { get; } public ReadOnlySpan FirstSpan { get; } public SequencePosition Start { get; } public SequencePosition End { get; } public ReadOnlySequence(T[] array); public ReadOnlySequence(T[] array, int start, int length); public ReadOnlySequence(ReadOnlyMemory memory);
public ReadOnlySequence(ReadOnlySequenceSegmentstartSegment, int startIndex, ReadOnlySequenceSegment endSegment, int endIndex); public ReadOnlySequence Slice(long start, long length); public ReadOnlySequence Slice(long start, SequencePosition end); public ReadOnlySequence Slice(SequencePosition start, long length); public ReadOnlySequence Slice(int start, int length); public ReadOnlySequence Slice(int start, SequencePosition end); public ReadOnlySequence Slice(SequencePosition start, int length); public ReadOnlySequence Slice(SequencePosition start, SequencePosition end); public ReadOnlySequence Slice(SequencePosition start); public ReadOnlySequence Slice(long start); public Enumerator GetEnumerator(); public SequencePosition GetPosition(long offset);
public long GetOffset(SequencePosition position);
public SequencePosition GetPosition(long offset, SequencePosition origin); public bool TryGet(ref SequencePosition position, out ReadOnlyMemorymemory, bool advance = true); }
利用定义的若干Slice方法重载,我们可以对一个ReadOnlySequence
四、创建“多段式”ReadOnlySequence
“单段式”ReadOnlySequence
var segment1 = new BufferSegment<int>([7, 8, 9]); var segment2 = new BufferSegment<int>([4, 5, 6], segment1); var segment3 = new BufferSegment<int>([1, 2, 3], segment2); var index = 1; foreach (var memory in new ReadOnlySequence<int>(segment3, 0, segment1, 3)) { var span = memory.Span; for (var i = 0; i < span.Length; i++) { Debug.Assert(span[i] == index++); } } public sealed class BufferSegment: ReadOnlySequenceSegment { public BufferSegment(T[] array, BufferSegment ? next = null) : this(new ReadOnlyMemory (array), next) { } public BufferSegment(T[] array, int start, int length, BufferSegment ? next = null) : this(new ReadOnlyMemory (array, start, length), next) { } public BufferSegment(ReadOnlyMemory memory, BufferSegment ? next = null) { Memory = memory; Next = next; BufferSegment ? current = next; while (current is not null) { current.RunningIndex += memory.Length; current = current.Next as BufferSegment ; } } }
五、高效读取ReadOnlySequence
由于ReadOnlySequence
static bool TryReadInt32(ref ReadOnlySequence<byte> buffer, out int? value) { if (buffer.Length < 4) { value = null; return false; } var slice = buffer.Slice(buffer.Start, 4); if (slice.IsSingleSegment) { value = BinaryPrimitives.ReadInt32BigEndian(slice.FirstSpan); } else { Span<byte> bytes = stackalloc byte[4]; slice.CopyTo(bytes); value = BinaryPrimitives.ReadInt32BigEndian(bytes); } buffer = buffer.Slice(slice.End); return true; },
其实针对ReadOnlySequence
static bool TryReadInt32(ref ReadOnlySequence<byte> buffer, out int? value) { var reader = new SequenceReader<byte>(buffer); if (reader.TryReadBigEndian(out int v)) { value = v; buffer = buffer.Slice(4); return true; } value = null; return false; }

