针对“缓冲区”编程是一个非常注重“性能”的地方,我们应该尽可能地避免武断地创建字节数组来存储读取的内容,这样不但会导致大量的字节拷贝,临时创建的字节数组还会带来GC压力。要正确、高效地读写缓冲内容,我们应该对几个我们可能熟悉的类型具有更深的认识。
一、Array、ArraySegment、Span
、Memory 与String二、MemoryManager 三、ReadOnlySequence 四、创建“多段式”ReadOnlySequence 五、高效读取ReadOnlySequence
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(MemoryManager manager, int length);
internal Memory(MemoryManager manager, int start, int length);
}
Span
从上面给出的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
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(ReadOnlySequenceSegment startSegment, 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 ReadOnlyMemory memory, bool advance = true);
}
利用定义的若干Slice方法重载,我们可以对一个ReadOnlySequence
“单段式”ReadOnlySequence
var segment1 = new BufferSegment([7, 8, 9]);
var segment2 = new BufferSegment([4, 5, 6], segment1);
var segment3 = new BufferSegment([1, 2, 3], segment2);
var index = 0;
foreach (var memory in new ReadOnlySequence(segment3, 0, segment1, 4))
{
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;
var current = next;
while (current is not null)
{
current.RunningIndex += memory.Length;
}
}
}
由于ReadOnlySequence
static bool TryReadInt32(ref ReadOnlySequence 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 bytes = stackalloc byte[4];
slice.CopyTo(bytes);
value = BinaryPrimitives.ReadInt32BigEndian(bytes);
}
buffer = buffer.Slice(slice.End);
return true;
},
其实针对ReadOnlySequence
static bool TryReadInt32(ref ReadOnlySequence buffer, out int? value)
{
var reader = new SequenceReader(buffer);
if (reader.TryReadBigEndian(out int v))
{
value = v;
buffer = buffer.Slice(4);
return true;
}
value = null;
return false;
}
文章转载自:Artech
原文链接:https://www.cnblogs.com/artech/p/18019333/array_memory_sequence
体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构