C# Object Pool Algorithm

2022年10月24日 70点热度 3人点赞 0条评论
内容目录

使用的类型是结构体,如果是对象,则在创建内存块的时候,需要使用别的方式。子所以使用块的形式而不是直接管理一个对象,是基于多个方面考虑的。
1,使用块的形式,可以一次性分配连续的内存;如果逐个分配,会导致碎片太多、每次分配都需要时间;

缺点:
1,不能扩增或减少对象数量、块大小;
2,以块的形式存在,用户用完一个块后,需要切换到下一个块;

这两个缺点都是很容易解决的。

然后笔者后面发现了几个不得了的 API,因此改进了对象池算法,对结构体做了优化。
可以看:https://www.whuanle.cn/archives/20892

对象池算法

结构体类型:

public struct MyTestType  
{  
	public string? Key{get;set;}  
	// ... ...  
}  

创建内存块表示,每个内存块的对象数量都一致,不存在具有差异数量的内存块。

// 一个对象内存块,里面的对象数量是固定的  
internal sealed class MemoryPoolBlock : IMemoryOwner<MyTestType>  
{  
    private readonly PinnedBlockMemoryPool _pool;  

    internal MemoryPoolBlock(PinnedBlockMemoryPool pool, int length)  
    {  
        _pool = pool;  
        // 在内存中创建内存位置连续的、指定数量的对象,只适合值类型  
        Memory = MemoryPool<MyTestType>.Shared.Rent(length).Memory;  
    }  

    public Memory<MyTestType> Memory  
    {  
        get;  
    }  

    // 释放此内存块  
    public void Dispose()  
    {  
        _pool.Return(this);  
    }  

    // 重置每个对象  
    public void Reset()  
    {  
        Memory.Span.Clear();  
    }  
}  

MemoryPool<MyTestType>.Shared.Rent(length) 的方式只对值类型有效。

为了管理这些内存块,需要做一个管理类,方便每个块的分配和释放:

// 内存块管理器  
public sealed class PinnedBlockMemoryPool : MemoryPool&lt;MyTestType&gt;  
{  
    // 固定每个内存块的对象数量  
    private const int BlockSize = 200;  
    public override int MaxBufferSize { get; } = BlockSize;  

    private readonly ConcurrentQueue&lt;MemoryPoolBlock&gt; _blocks = new();  

    private bool _isDisposed;  
    private readonly object _disposeSync = new();  

    private const int AnySize = -1;  

    public override IMemoryOwner&lt;MyTestType&gt; Rent(int size = AnySize)  
    {  
        // size 没有实际意义,所有 block 都是固定大小  
        if (size &gt; BlockSize)  
        {  
            throw new Exception(&quot;申请的对象数量体积过大!&quot;);  
        }  

        if (_isDisposed)  
        {  
            throw new Exception(&quot;已经被销毁!&quot;);  
        }  

        if (_blocks.TryDequeue(out var block))  
        {  
            return block;  
        }  

        return new MemoryPoolBlock(this, BlockSize);  
    }  

    internal void Return(MemoryPoolBlock block)  
    {  
        if (!_isDisposed)  
        {  
            block.Reset();  
            _blocks.Enqueue(block);  
        }  
    }  

    protected override void Dispose(bool disposing)  
    {  
        if (_isDisposed) return;  

        lock (_disposeSync)  
        {  
            _isDisposed = true;  

            if (!disposing) return;  
            while (_blocks.TryDequeue(out _))  
            {  
            }  
        }  
    }  
}  

因为当前是以每个块的方式使用的,因此如果每个块使用完成后,需要切换下一个块,这样使用的时候会带来麻烦。

// 对象池,屏蔽 MemoryPoolBlock、PinnedBlockMemoryPool,减少使用难度  
public class ObjectPool : IDisposable  
{  
    private PinnedBlockMemoryPool _pool;  
    private readonly List&lt;MemoryPoolBlock&gt; _blocks = new();  

    public ObjectPool(PinnedBlockMemoryPool pool)  
    {  
        _pool = pool;  
        _currentBlock = (pool.Rent(pool.MaxBufferSize) as MemoryPoolBlock)!;  
        _blocks.Add(_currentBlock);  
    }  

    private volatile MemoryPoolBlock _currentBlock;  
    private volatile int _currentIndex = 0;  
    public ref MyTestType Get()  
    {  
        if (_currentIndex &gt;= _currentBlock.Memory.Length)  
        {  
            _currentBlock = (_pool.Rent(_pool.MaxBufferSize) as MemoryPoolBlock)!;  
            _blocks.Add(_currentBlock);  
            _currentIndex = 0;  
        }  
        ref MyTestType p = ref MemoryMarshal.GetReference(_currentBlock.Memory.Span);  
        ref MyTestType field = ref Unsafe.Add(ref p, _currentIndex);  
        Interlocked.Add(ref _currentIndex, 1);  
        return ref field;  
    }  

    private bool _isDisposed;  

    public void Dispose()  
    {  
        if (_isDisposed) return;  
        _isDisposed = true;  
        foreach (var block in _blocks)  
        {  
            _pool.Return(block);  
        }  

        _pool = null!;  
    }  
}  

使用:

 // 全局静态  
	private static readonly PinnedBlockMemoryPool Pool;  

每次使用使用:  
using var pool = new ObjectPool(Pool);  
ref var obj = ref pool.Get();  

ArrayPool

 byte[] data = ArrayPool&lt;byte&gt;.Shared.Rent(initialSize);  
 ArrayPool&lt;byte&gt;.Shared.Return(data);  

痴者工良

高级程序员劝退师

文章评论