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里大量應用。