Object Pooling(对象池)实现

在文章开始之前首先要思考的问题是为什么要建立对象池。这和.NET垃圾回收机制有关,正如下面引用所说,内存不是无限的,垃圾回收器最终要回收对象,释放内存。尽管.NET为垃圾回收已经进行了大量优化,例如将托管堆划分为 3 Generations(代)并设定新建的对象回收的最快,新建的短生命周期对象将进入 Gen 0(新建对象大于或等于 85,000 字节将被看作大对象,直接进入 Gen 2),而 Gen 0 通常情况下分配比较小的内存,因此Gen 0 将回收的非常快。而高频率进行垃圾回收导致 CPU 使用率过高,当 Gen 2 包含大量对象时,回收垃圾也将产生性能问题。

.NET 的垃圾回收器管理应用程序的内存分配和释放。 每当有对象新建时,公共语言运行时都会从托管堆为对象分配内存。 只要托管堆中有地址空间,运行时就会继续为新对象分配空间。 不过,内存并不是无限的。 垃圾回收器最终必须执行垃圾回收来释放一些内存。 垃圾回收器的优化引擎会根据所执行的分配来确定执行回收的最佳时机。 执行回收时,垃圾回收器会在托管堆中检查应用程序不再使用的对象,然后执行必要的操作来回收其内存。参考

构造对象池

.Net Core 在(Base Class Library)基础类型中添加了 ArrayPool,但 ArrayPool 只适用于数组。针对自定义对象,参考MSDN有一个实现,但没有初始化池大小,且从池里取对象的方式比较粗糙,完整的对象池应该包含:

  • 池大小
  • 初始化委托
  • 实例存取方式(FIFO、LIFO 等自定义方式,根据个人需求实现获取实例方式)
  • 获取实例策略

1. 定义对象存取接口,以实现多种存取策略,例如 FIFO、LIFO

/// <summary>
/// 对象存取方式
/// </summary>
public interface IAccessMode<T>
{
    /// <summary>
    /// 租用对象
    /// </summary>
    /// <returns></returns>
    /// <exception cref="InvalidOperationException"></exception>
    T Rent();

    /// <summary>
    /// 返回实例
    /// </summary>
    /// <param name="item"></param>
    void Return(T item);
}

2. 实现存取策略

FIFO

FIFO通过Queue实现,参考

public sealed class FIFOAccessMode<T> : Queue<T>, IAccessMode<T>
{
    private readonly int _capacity;
    private readonly Func<T> _func;
    private int _count;

    public FIFOAccessMode(int capacity, Func<T> func) : base(capacity)
    {
        _capacity = capacity;
        _func = func;
        InitialQueue();
    }

    public T Rent()
    {
        Interlocked.Increment(ref _count);
        return _capacity < _count ? _func.Invoke() : Dequeue();
    }

    public void Return(T item)
    {
        if (_count > _capacity)
        {
            var disposable = (IDisposable)item;
            disposable.Dispose();
        }
        else
        {
            Enqueue(item);
        }
        Interlocked.Decrement(ref _count);
    }

    private void InitialQueue()
    {
        for (var i = 0; i < _capacity; i++)
        {
            Enqueue(_func.Invoke());
        }
    }
}
LIFO

在LIFO中借助Stack特性实现进栈出栈,因此该策略继承自Stack,参考

public sealed class LIFOAccessModel<T> : Stack<T>, IAccessMode<T>
{
    private readonly int _capacity;
    private readonly Func<T> _func;
    private int _count;

    public LIFOAccessModel(int capacity, Func<T> func) : base(capacity)
    {
        _capacity = capacity;
        _func = func;
        InitialStack();
    }

    public T Rent()
    {
        Interlocked.Increment(ref _count);
        return _capacity < _count ? _func.Invoke() : Pop();
    }

    public void Return(T item)
    {
        if (_count > _capacity)
        {
            var disposable = (IDisposable)item;
            disposable.Dispose();
        }
        else
        {
            Push(item);
        }
        Interlocked.Decrement(ref _count);
    }

    private void InitialStack()
    {
        for (var i = 0; i < _capacity; i++)
        {
            Push(_func.Invoke());
        }
    }
}

注意:以上两个实现都遵循池容量不变原则,但租用的实例可以超过对象池大小,返还时还将检测该实例直接释放还是进入池中。而如何控制池大小和并发将在下面说明。

3.Pool实现

public class Pool<T> : IDisposable where T : IDisposable
{
    private int _capacity;
    private IAccessMode<T> _accessMode;
    private readonly object _locker = new object();
    private readonly Semaphore _semaphore;

    public Pool(AccessModel accessModel, int capacity, Func<T> func)
    {
        _capacity = capacity;
        _semaphore = new Semaphore(capacity, capacity);
        InitialAccessMode(accessModel, capacity, func);
    }

    private void InitialAccessMode(AccessModel accessModel, int capacity, Func<T> func)
    {
        switch (accessModel)
        {
            case AccessModel.FIFO:
                _accessMode = new FIFOAccessMode<T>(capacity, func);
                break;
            case AccessModel.LIFO:
                _accessMode = new LIFOAccessModel<T>(capacity, func);
                break;
            default:
                throw new NotImplementedException();
        }
    }

    public T Rent()
    {
        _semaphore.WaitOne();
        return _accessMode.Rent();
    }

    public void Return(T item)
    {
        _accessMode.Return(item);
        _semaphore.Release();
    }

    public void Dispose()
    {
        if (!typeof(IDisposable).IsAssignableFrom(typeof(T))) return;

        lock (_locker)
        {
            while (_capacity > 0)
            {
                var disposable = (IDisposable)_accessMode.Rent();
                _capacity--;
                disposable.Dispose();
            }

            _semaphore.Dispose();
        }
    }
}

在Pool中如何控制程序池并发,这里我们引入了 Semaphore 以控制并发,这里将严格控制程序池大小,避免内存溢出。

4.使用

Student 类用作测试

public class Student : IDisposable
{
    public string Name { get; set; }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private bool _disposed;

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed)
            return;

        if (disposing)
        {
            Name = null;
             //Free any other managed objects here.
        }

        _disposed = true;
    }
}
public void TestPool()
{
    Func<Student> func = NewStudent;
    var pool = new Pool<Student>(AccessModel.FIFO, 2, func);
    for (var i = 0; i < 3; i++)
    {
        Student temp = pool.Rent();
        //todo:Some operations
        pool.Return(temp);
    }

    Student temp1 = pool.Rent();

    pool.Return(temp1);

    pool.Dispose();
}

public Student NewStudent()
{
    return new Student();
}

总结:至此,一个完整的对象池建立完毕。

原文地址:https://www.cnblogs.com/Zhang-Xiang/p/10369667.html

时间: 2024-10-21 18:58:39

Object Pooling(对象池)实现的相关文章

Object Pool 对象池的C++11使用(转)

很多系统对资源的访问快捷性及可预测性有严格要求,列入包括网络连接.对象实例.线程和内存.而且还要求解决方案可扩展,能应付存在大量资源的情形. object pool针对特定类型的对象循环利用,这些对象要么创建开销巨大,要么可创建的数量有限.而且在pool中的对象需要做到无状态. 然后转了这位博主的代码,还在研究中 const int MaxObjectNum = 10; template <typename T> class ObjectPool { template <typename

[译]Unity3D内存管理——对象池(Object Pool)

从一个简单的对象池类开始说起 对象池背后的理念其实是非常简单的.我们将对象存储在一个池子中,当需要时在再次使用,而不是每次都实例化一个新的对象.池的最重要的特性,也就是对象池设计模式的本质是允许我们获取一个“新的”对象而不管它真的是一个新的对象还是循环使用的对象.该模式可以用以下简单的几行代码实现: public class ObjectPool<T> where T : class, new() { private Stack<T> m_objectStack = new Sta

Unity内存管理之对象池(Object Pool)

第一篇博客,打算和大家分享一下关于Unity开发当中用到的对象池,那么何为对象池呢? [对象池]:对象池是一种经常用到的内存管理服务,它的作用在于可以减少从头创建每个对象的系统开销. 众所周知,在Unity游戏开发的过程中经常会创建一些新的对象,如果数量较少还可以接受,如果创建的新对象数量庞大,那么对内存而言是一个极大的隐患.例如射击游戏当中,每发射一颗子弹,都要创建一个新的子弹对象,那么子弹是数量庞大,可想而知一场游戏当中会创建多少这样的新对象,那么如果这些子弹创建之后都对游戏起着关键且持续性

对象池实现分析

对象池实现分析 什么是对象池技术?对象池应用在哪些地方? 对象池其实就是缓存一些对象从而避免大量创建同一个类型的对象,类似线程池的概念.对象池缓存了一些已经创建好的对象,避免需要时才创建对象,同时限制了实例的个数.池化技术最终要的就是重复的使用池内已经创建的对象.从上面的内容就可以看出对象池适用于以下几个场景: 创建对象的开销大 会创建大量的实例 限制一些资源的使用 如果创建一个对象的开销特别大,那么提前创建一些可以使用的并且缓存起来(池化技术就是重复使用对象,提前创建并缓存起来重复使用就是池化

对象池的实现与性能测试

引用对象池的好处:从池中操作对象比直接new.free要性能更快,且能避免内存碎片的堆积 先贴对象池的代码: namespace LegendServer.Util { //对象基 public abstract class ObjectBase { public abstract void Init(); } //对象池管理器(采用堆栈存储,支持动态扩容,支持多线程,新扩容的则自动加入到池中能被重复利用) public class ObjectPoolManager<T> where T :

深度剖析C++对象池自动回收技术实现

http://www.tuicool.com/articles/mQBfQfN 对象池可以显著提高性能,如果一个对象的创建非常耗时或非常昂贵,频繁去创建的话会非常低效.对象池通过对象复用的方式来避免重复创建对象,它会事先创建一定数量的对象放到池中,当用户需要创建对象的时候,直接从对象池中获取即可,用完对象之后再放回到对象池中,以便复用.这种方式避免了重复创建耗时或耗资源的大对象,大幅提高了程序性能.本文将探讨对象池的技术特性以及源码实现. 对象池类图 ObjectPool:管理对象实例的pool

对象池的设计

对象池的设计及其实现 对象池概述: 对象池模型创建并拥有固定数量的对象,当程序需要一个新的对象时,如果对象池中有空闲对象,则立即返回,否则才创建新的该类对象.当一个对象不再被使用时,其应该应该将其放回对象池,以便后来的程序使用.由于系统资源有限,一个对象池模型应该指定其可容纳的最大对象数量.当达到该数量时,如果仍然有对象创建请求,则抛出异常或者阻塞当前调用线程,直到一个对象被放回对象池中. 对象池模型适用的场景: (1)需要使用大量对象 (2)这些对象的实例化开销比较大且生存期比较短 对象池优势

Netty轻量级对象池实现分析

什么是对象池技术?对象池应用在哪些地方? 对象池其实就是缓存一些对象从而避免大量创建同一个类型的对象,类似线程池的概念.对象池缓存了一些已经创建好的对象,避免需要时才创建对象,同时限制了实例的个数.池化技术最终要的就是重复的使用池内已经创建的对象.从上面的内容就可以看出对象池适用于以下几个场景: 创建对象的开销大 会创建大量的实例 限制一些资源的使用 如果创建一个对象的开销特别大,那么提前创建一些可以使用的并且缓存起来(池化技术就是重复使用对象,提前创建并缓存起来重复使用就是池化)可以降低创建对

对象池的设计及其实现

对象池概述: 对象池模型创建并拥有固定数量的对象,当程序需要一个新的对象时,如果对象池中有空闲对象,则立即返回,否则才创建新的该类对象.当一个对象不再被使用时,其应该应该将其放回对象池,以便后来的程序使用.由于系统资源有限,一个对象池模型应该指定其可容纳的最大对象数量.当达到该数量时,如果仍然有对象创建请求,则抛出异常或者阻塞当前调用线程,直到一个对象被放回对象池中. 对象池模型适用的场景: (1)需要使用大量对象 (2)这些对象的实例化开销比较大且生存期比较短 对象池优势: 一个对象池可以在可