C# 对象池算法

2022年10月24日 1598点热度 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<MyTestType>
{
    // 固定每个内存块的对象数量
    private const int BlockSize = 200;
    public override int MaxBufferSize { get; } = BlockSize;

    private readonly ConcurrentQueue<MemoryPoolBlock> _blocks = new();

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

    private const int AnySize = -1;

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

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

        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<MemoryPoolBlock> _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 >= _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<byte>.Shared.Rent(initialSize);
 ArrayPool<byte>.Shared.Return(data);

痴者工良

高级程序员劝退师

文章评论