zoukankan      html  css  js  c++  java
  • .Net性能调优-MemoryPool

    简单用法

    //获取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 来租用数组

    实现代码如下:

    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>

    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)

    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的场景还请在下面留言告诉我一下是什么场景,互相学习一下,嘿嘿

  • 相关阅读:
    一个小企业招人的胡思乱想
    7 搜索关键词 && 提问
    6. 搜索&&学习常用站点
    5. Github的使用
    2020 renew 博客目录
    4. 版本控制
    3. 项目管理规范 && 命名方式规范
    2.7 C#语法的学习(小结)
    2.6 C#语法的学习(六) && 异常处理 && 调试
    2.5 C#语法的学习(五) && 判断
  • 原文地址:https://www.cnblogs.com/bluesummer/p/15268230.html
Copyright © 2011-2022 走看看