dotnet高性能buffer

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) short
WriteBigEndian(this IBufferWriter, int) int
WriteBigEndian(this IBufferWriter, long) long
WriteBigEndian(this IBufferWriter, ushort) ushort
WriteBigEndian(this IBufferWriter, uint) uint
WriteBigEndian(this IBufferWriter, ulong) ulong
WriteBigEndian(this IBufferWriter, float) float
WriteBigEndian(this IBufferWriter, double) double
WriteLittleEndian(this IBufferWriter, short) short
WriteLittleEndian(this IBufferWriter, int) int
WriteLittleEndian(this IBufferWriter, long) long
WriteLittleEndian(this IBufferWriter, ushort) ushort
WriteLittleEndian(this IBufferWriter, uint) uint
WriteLittleEndian(this IBufferWriter, ulong) ulong
WriteLittleEndian(this IBufferWriter, float) float
WriteLittleEndian(this IBufferWriter, double) 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裏大量應用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章