前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >.Net性能调优-MemoryPool

.Net性能调优-MemoryPool

作者头像
蓝夏
发布2021-09-23 14:42:43
3871
发布2021-09-23 14:42:43
举报
文章被收录于专栏:bluesummer

简单用法

代码语言:javascript
复制
//获取MemoryPool实例,实际返回了一个ArrayMemoryPool<T>
MemoryPool<char> Pool = MemoryPool<char>.Shared;

//加上using
using IMemoryOwner<char> owner = Pool.Rent(1024 * 900);

Memory<char> memory = owner.Memory;
for (int i = 0; i < 1024 * 900; i++)
{
	memory.Span[i] = 'x';
}

//从100的下标开始截取10个字符
var sliceArr = memory.Span.Slice(100, 10).ToArray();

ArrayMemoryPool<T>

通过MemoryPool<int>.Shared我们可以获取到一个MemoryPool<T>的示例,该实例的类型为ArrayMemoryPool<T>

ArrayMemoryPool<T>实际上只有一个函数可用,就是Rent(),还有一个Dispose()函数但是里面没有任何代码

Rent()限制了最大租借长度:2147483647u=(1024^3*2-1)=(2G-1bytes),并返回一个IMemoryOwner<T>对象

ArrayMemoryPoolBuffer<T>

ArrayMemoryPool<T>Rent()实际返回了一个ArrayMemoryPoolBuffer<T>,该类继承了IMemoryOwner<T>

它提供一个Memroy属性来获取一个Memory<T>对象,我们可以借助Memroy<T>来操控我们租用的数组了

ArrayMemoryPool<T>的内部实际还是调用的ArrayPool<T>.Shared 来租用数组

实现代码如下:

代码语言:javascript
复制
public Memory<T> Memory
{
    get
    {
        T[] array = _array;
        if (array == null)
        {
            System.ThrowHelper.ThrowObjectDisposedException_ArrayMemoryPoolBuffer();
        }	
        return new Memory<T>(array);
    }
}

public ArrayMemoryPoolBuffer(int size)
{
    _array = ArrayPool<T>.Shared.Rent(size);
}

public void Dispose()
{
    T[] array = _array;
    if (array != null)
    {
        _array = null;
        ArrayPool<T>.Shared.Return(array);
    }
}

Memory<T>

Memory<T>的构造函数接收一个array<T>,存在私有变量_object中。Memory中对数组的操作最终又依赖于Span<T>

代码语言:javascript
复制
public readonly struct Memory<T> : IEquatable<Memory<T>>
{
	private readonly object _object;

	private readonly int _index;

	private readonly int _length;

	public static Memory<T> Empty => default(Memory<T>);

	public int Length => _length;

	public bool IsEmpty => _length == 0;

	public unsafe Span<T> Span=>{ _object...}
	
	public T[] ToArray()
	{
		return Span.ToArray();
	}
}

Slice(start,length)

代码语言:javascript
复制
public Memory<T> Slice(int start)
{
    return new Memory<T>(_object, _index + start, _length - start);
}

Memory的Slice函数可以对数组进行截取,该函数仍然返回一个Memory<T>对象,新的对象记录了原始的_object和要切割的indexlength,所以该函数不会造成额外的内存消耗

Pin()

该函数的作用是获取数组内存的管理权,不让垃圾回收器回收,自己管理内存,但他怎么自己管理的,暂时木有研究。。。有兴趣的小伙伴可以自行研究。

但是因为我们的数组是从ArrayPool<T>租借的,由ArrayMemoryPool<T>Dispose函数回收的,所以本文对于Memory的使用方式中并不会用到该函数

所以综上所述,简单的理解就是Memory<T>主要是用来管理Span<T>

那么Span又是啥呢?为什么Memrory<T>不直接提供操作数组的方式,而是要返回一个Span<T>呢?

啊哈哈,这个就问倒我了

简单地概括其原因就是通过Span<T>来操作数组切割分片赋值等,更快,更节约内存,数据流转无复制。而Span本身过于底层,使用方面有很多的局限性。所以最终改用Memory<T>来控制Span<T>的生命周期,而只给用户提供Span的操作数组的相关函数。

至于它怎么底层了,局限性在哪些方面,想深入研究的话网上有很多相关的优秀文章值得阅读一下。对于Span的学习我现在是适可而止,后面把内存需要学习的相关知识学的差不多的时候再回来研究它吧,欠的债早晚是要还的啊...

使用场景

啊这...,对于MemoryPool,确实没有想到使用场景。因为之前的文章曾经介绍过ArrayPool。而MemoryPool的数组又是依赖ArrayPool创建的,反而相比于ArrayPool他又多了创建IMemoryOwner的消耗。这里推荐一篇文章对二者进行了对比:https://endjin.com/blog/2020/09/arraypool-vs-memorypool-minimizing-allocations-ais-dotnet。

其实我们更需要的是Memory<T>和Span<T>,在System.Memory命名空间下的MemoryExtensions为我们的Array[]提供了扩展方法AsMemory\<T\>(this T[]? array) AsSpan\<T\>(this T[]? array)等等几十种扩展,足够满足你的需要

所以如果不是非得要用MemoryPool的场景还是推荐直接使用ArrayPool吧,当然如果你有必须使用MemoryPool的场景还请在下面留言告诉我一下是什么场景,互相学习一下,嘿嘿

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-09-14 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简单用法
  • ArrayMemoryPool<T>
  • ArrayMemoryPoolBuffer<T>
  • Memory<T>
  • 使用场景
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档