1 前言
我曾經寫過《雜談.netcore的Buffer相關新型別》的部落格,簡單介紹過BinaryPrimitives、Span<>,Memory<>,ArrayPool<>,Memorypool<>這些基礎型別,在實際專案中,我們更需要的是更上層的高效緩衝區申請、buffer寫入、buffer讀取功能。本文將介紹如何利用這些基礎型別,封裝成易於使用的buffer相關操作類,這些類的原始碼在MemoryExtensions庫裡。
2 buffer知識
buffer的申請
通過經驗與實驗資料,根據不同場景與buffer大小,選擇合適的申請方式。
申請式 | 特點 | 侷限 |
---|---|---|
stackalloc byte | 非常快速 | 堆疊上分配記憶體塊,容量小且在方法返回時緩衝區丟棄 |
new byte[] | 當小於1KB時速度快 | 頻繁建立導致記憶體碎片,GC壓力大 |
ArrayPool.Rent | 適合大的緩衝區租賃,幾乎無記憶體分配 | 緩衝區小於1KB時,租賃不如new來得快 |
IBufferWriter介面
此介面支援獲取緩衝區的寫入Span或GetMemory給外部直接寫入資料,寫入完成之後呼叫Advance(int)方法,告訴writer實際的寫入大小。
我們來對比一下MemoryStream的Write()方法,比如要寫一個int型別的值,我們不得不將int轉為4位元組的byte[],然後傳byte[]到Write()方法。這個4位元組的byte[]是一個副作用,它的存在原於外部無法獲取和擴大MemoryStream的緩衝區。
3 BufferWriter的實現
根據“buffer的申請”幾種方式,我們實現多種不同的BufferWriter。
RecyclableBufferWriter
可回收的自動擴容BufferWriter,適合於大的緩衝區的場景。它的緩衝區通過ArrayPool來租賃,用完之後,要Dispose()歸還到ArrayPool。優點是記憶體分配少,缺點是租賃比直接建立小的緩衝區還要慢。
var writer = new RecyclableBufferWriter<byte>(4);
writer.Write((byte)1);
writer.Write(new byte[] { 2, 3, 4 });
writer.WriteBigEndian(int.MaxValue);
var writtern = writer.WrittenSpan; // 1,2,3,4,127,255,255,255
// return the buffer to pool
writer.Dispose();
ResizableBufferWriter
自動擴容的BufferWriter,適合小的動態緩衝區的場景。它的衝區通過new Array來建立,通過Array.Resize擴容。優點是cpu效能好,缺點是記憶體分配高。
var writer = new ResizableBufferWriter<byte>(4);
writer.Write((byte)1);
writer.Write(new byte[] { 2, 3, 4 });
writer.WriteBigEndian(int.MaxValue);
var writtern = writer.WrittenSpan; // 1,2,3,4,127,255,255,255
FixedBufferWriter
固定大小緩衝區,就是我們自己new的Array,包裝為IBufferWriter物件。
var array = new byte[16];
var writer = array.CreateWriter();
writer.WriteBigEndian(18);
writer.WriteBigEndian(2.01f);
4 IBufferWriter的擴充套件
經常會遇到將int、double等諸多數字型別寫入IBufferWriter的場景,期間還涉及平臺的BigEndian或LittleEndian,我們給IBufferWriter<byte>
編寫過載的擴充套件方法。
方法 | 說明 |
---|---|
WriteBigEndian(this IBufferWriter |
short |
WriteBigEndian(this IBufferWriter |
int |
WriteBigEndian(this IBufferWriter |
long |
WriteBigEndian(this IBufferWriter |
ushort |
WriteBigEndian(this IBufferWriter |
uint |
WriteBigEndian(this IBufferWriter |
ulong |
WriteBigEndian(this IBufferWriter |
float |
WriteBigEndian(this IBufferWriter |
double |
WriteLittleEndian(this IBufferWriter |
short |
WriteLittleEndian(this IBufferWriter |
int |
WriteLittleEndian(this IBufferWriter |
long |
WriteLittleEndian(this IBufferWriter |
ushort |
WriteLittleEndian(this IBufferWriter |
uint |
WriteLittleEndian(this IBufferWriter |
ulong |
WriteLittleEndian(this IBufferWriter |
float |
WriteLittleEndian(this IBufferWriter |
double |
5 ref BufferReader
同樣的,我們也經常遇到從緩衝區中讀取為int、double等諸多數字型別的場景,所以也需要設計一個高效的BufferReader。
public ref struct BufferReader
{
/// <summary>
/// 未讀取的資料
/// </summary>
private ReadOnlySpan<byte> span;
}
給它設計ReadLittleEndian和ReadBigEndian相關Api
方法 | 說明 |
---|---|
ReadBigEndian(out short) | short |
ReadBigEndian(out int) | int |
ReadBigEndian(out long) | long |
ReadBigEndian(out ushort) | ushort |
ReadBigEndian(out uint) | uint |
ReadBigEndian(out ulong) | ulong |
ReadBigEndian(out float) | float |
ReadBigEndian(out double) | double |
ReadLittleEndian(out short) | short |
ReadLittleEndian(out int) | int |
ReadLittleEndian(out long) | long |
ReadLittleEndian(out ushort) | ushort |
ReadLittleEndian(out uint) | uint |
ReadLittleEndian(out ulong) | ulong |
ReadLittleEndian(out float) | float |
ReadLittleEndian(out double) | double |
6 關於MemoryExtensions庫
本文提到的這些類或結構體,在MemoryExtensions庫裡都有實現,可以直接使用,其中BufferWriter技術已經在WebApiClient裡大量應用。