最近研究了一下.net core 2.1的基础类库,发现它引入了一个System.Buffers名字空间,里面提供了一系列比较实用的对象,便简单的管中窥豹浏览一下。

ArrayPool<T>

ArrayPool<T>是一个数组类型的对象池,本身ArrayPoo<T>是一个抽象类,但他有一个默认的实现ArrayPoo<T>. Shared,使用方法如下:

var pool   = ArrayPool<byte>.Shared;
var buffer = pool.Rent(2048);
try
{
    //使用buffer
}
finally
{
    pool.Return(buffer);
    //归还buffer后不要再使用
}

方法比较简单:

  1. 通过Rent从对象池中申请buffer
  2. 使用完后,通过Return将buffer归还至对象池

微软的文档上并没有详细描述默认的ArrayPoo<T>的对象申请算法,但由于其代码是开源的,还是可以到github上看到其实现方式的。

初略的看了一下,貌似也并不复杂,和传统的对象池的维护方式也差不多:

  1. 系统维持着一个对象池
  2. 调用Rent的时候,首先会到对象池中查看是否有合适的对象(至少要满足最小长度),如果有则直接返回对象池中的对象,并将其从对象池中移除
  3. 调用Return时,将对象放置到对象池,从而可以作为下次Rent的候选对象

注:这里只记录了主要相关功能,实际算法比这个复杂。另外由于没有很详细看实现方式,如要描述不正确的地方欢迎指正

也就是说,return后的对象,很可能被别的地方rent走,因此可能导致读写冲突。(类似于c语言中的野指针,但仍然是安全的,不会造成内存错误)

MemoryPool<T>

除了ArrayPool外,System.Buffers名字空间下还提供了一个MemoryPool,它的使用方式和ArraPool比较类似,基本示例如下:

var pool   = MemoryPool<byte>.Shared;
var buffer = pool.Rent(2048);
try
{
    var memory = buffer.Memory;
    //use Memory<byte>
}
finally
{
    buffer.Dispose();
}

整个过程还是非常类似的,不过释放的时候是调用的Dispose方法,用起来实际要更加方便点。不过这里申请到的是Memory<T>对象,可能有的地方不像byte[]那样适用 。

关于MemoryPool的实现,我在github上找了一下,还没有看到。不过由于它返回的是Memory<T>,理论上来讲应该是有更高的效率。(例如,可以把一个大段的buffer分成多个memory返回,从而减少申请新对象)

BinaryPrimitives

BinaryPrimitives位于System.Buffers.Binary名字空间下,它提供了一系列数字和字节互相转换的函数。

  .net core中的System.Buffers名字空间-LMLPHP

它的主要好处是是提供了常用BigEndian类型的数字的支持,在网络编程或者文件解析的方式的时候非常实用,免得造轮子了。

Utf8Parser、Utf8Formatter和Base64

这三个类位于System.Buffers.Text下,它主要用于utf8编码和base64编码下的常用类型的读写,如datetime,guid,bool等,并且支持常用的序列化方式。

这几个类目前官方文档都介绍的不是很详细,目前要详细了解的话只能看代码。由于篇幅所限,这里也不做更多的介绍,以后用到的时候再写单独的文章介绍它们。

参考文章:

  1. ArrayPool<T> Class
  2. Pooling large arrays with ArrayPool
05-06 01:14