内容目录
使用的类型是 结构体,如果是对象,则在创建内存块的时候,需要使用别的方式。
子所以使用块的形式而不是直接管理一个对象,是基于多个方面考虑的。
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);
文章评论