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

发表评论

评论已关闭。

相关文章