U3D客户端框架之类对象池技术优化C#语言GC

简介: 类对象池,类似对象池,顾名思义就是一定数量的已经创建好的类对象(Object)的集合。当需要创建对象时,先在池子中获取,如果池子中没有符合条件的对象,再进行创建新对象,同样,当对象需要销毁时,不做真正的销毁,而是将其对象SetActive(false),并存入池子中。这样就避免了大量对象的创建销毁,减少了GC,优化了性能。

一、类对象池概念


1.类对象池介绍


类对象池,类似对象池,顾名思义就是一定数量的已经创建好的类对象(Object)的集合。当需要创建对象时,先在池子中获取,如果池子中没有符合条件的对象,再进行创建新对象,同样,当对象需要销毁时,不做真正的销毁,而是将其对象SetActive(false),并存入池子中。这样就避免了大量对象的创建销毁,减少了GC,优化了性能。


2. 对象池解决什么问题?


可以最大限度的减少频繁创建销毁对象,减少GC次数,优化CPU,实现对象的缓存和复用,创建对象的成本比较大,并且创建比较频繁。对象池模式是一种创建型设计模式,它持有一个初始化好的对象的集合,将对象提供给调用者。


3.对象池的优缺点对比


a.对象池的优点:


运用对象池化技术可以显著地提升性能,尤其是当对象的初始化过程代价较大或者频率较高时。


一定程度上减少了GC的压力。对于实时性要求较高的程序有很大的帮助,比如说 Http 链接的对象池,Redis 对象池,资源加载实体类 等等都使用了对象池


b. 对象池弊端


脏对象的问题:所谓的脏对象就是指的是当对象被放回对象池后,还有对象在引用这个对象的内存地址。


脏对象可能带来两个问题:


1)脏对象持有上次使用的引用,导致引用出错。

2)脏对象如果下一次使用时没有做还原,可能导致数据出现问题,最终导致程序逻辑错误。


生命周期的问题:处于对象池中的对象生命周期一般是定期释放,如果无引用并且超时,该对象会被释放。维持大量的对象也是比较占用内存空间的,所以常驻对象数要选择合理的区间较好。


二、代码实现


ClassObjectPool.cs 实现代码


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Myh
{
    //类池(引用类型的类回收循环利用)
    public class ClassObjectPool:IDisposable
    {
        //类对象 在池中的 常驻数量
        public Dictionary<int, byte> ClassObjectCount
        {
            private set;
            get;
        }
        //类对象池缓存字典,key是hash_code,value是该类型的缓存队列
        public Dictionary<int, Queue<object>> m_dicClassObjectPool;
#if UNITY_EDITOR
        //在unity面板中显示的信息(类型:数量?)
        public Dictionary<Type, int> DicInspector = new Dictionary<Type, int>();
#endif
        public ClassObjectPool()
        {
            ClassObjectCount = new Dictionary<int, byte>();
            m_dicClassObjectPool = new Dictionary<int, Queue<object>>();
        }
        #region  设置类常驻数量
        //设置类常驻数量
        public void SetResideCount<T>(byte count) where T : class
        {
            //得到该类型的hashCode
            int key = typeof(T).GetHashCode();
            ClassObjectCount[key] = count;
        }
        #endregion
        //取出一个队列中的对象
        //模板类型约束,约束是类 或者是 结构体(class 和 struct都可以new,但是不能同时约束class和struct,第二个约束只能约束可以new)
        public T Dequeue<T>() where T : class,new()
        {
            //只能把引用类型的变量当成锁
            lock (m_dicClassObjectPool)
            {
                //先找到这个类的哈希(先算出这个类的哈希码)
                int key = typeof(T).GetHashCode();
                Queue<object> queue = null;
                //尝试取出这个类的缓存队列
                m_dicClassObjectPool.TryGetValue(key, out queue);
                //如果队列是空,说明没缓存过,new一个队列
                if (null == queue)
                {
                    queue = new Queue<object>();
                    m_dicClassObjectPool[key] = queue;
                }
                //如果队列里有缓存,取出
                if (queue.Count > 0)
                {
                    object obj = queue.Dequeue();
#if UNITY_EDITOR
                    Type t = obj.GetType();
                    if (DicInspector.ContainsKey(t))
                    {
                        DicInspector[t]--;
                    }
                    else
                    {
                        DicInspector[t] = 0;
                    }
#endif
                    //把刚刚从队列中取出的返回
                    return (T)obj;
                }
                else
                {
                    //如果队列无缓存,实例化一个
                    return new T();
                }
            }
        }
        //Enqueue 入队,把使用结束的类对象存入缓存中
        //对象回收
        public void Enqueue(object obj)
        {
            //使用 对象池map m_dicClassObjectPool 作为锁
            lock (m_dicClassObjectPool)
            {
                int key = obj.GetType().GetHashCode();
                Queue<object> queue = null;
                //取出该类型的队列
                m_dicClassObjectPool.TryGetValue(key,out queue);
#if UNITY_EDITOR
                Type t = obj.GetType();
                if (DicInspector.ContainsKey(t))
                {
                    DicInspector[t]++;
                }
                else
                {
                    DicInspector[t] = 1;
                }
#endif
                //如果不是从类对象池取出来的,视为无效,不可存入缓存中
                if (null != queue)
                {
                    queue.Enqueue(obj);
                }
            }
        }
        //释放对象池
        public void Release()
        {
            lock (m_dicClassObjectPool)
            {
                //队列的数量
                int queueCount = 0;
                //定义迭代器
                IEnumerator<KeyValuePair<int, Queue<object>>> iter = m_dicClassObjectPool.GetEnumerator();
                for (; iter.MoveNext();)
                {
                    //hash_code
                    int key = iter.Current.Key;
                    //拿到type对应的队列
                    Queue<object> queue = m_dicClassObjectPool[key];
#if UNITY_EDITOR
                    Type t = null;
#endif
                    queueCount = queue.Count;
                    //用户释放的时候 判断
                    byte resideCount = 0;
                    ClassObjectCount.TryGetValue(key,out resideCount);
                    //队列内的数量>持久化的数量才释放队列内的缓存
                    //队列内要保留 =resideCount 个的数量
                    while (queueCount > resideCount)
                    {
                        //队列中有可释放的对象
                        --queueCount;
                        //从队列中取出一个,这个对象没有任何引用,就变成了野指针 等待GC回收
                        object obj = queue.Dequeue();
#if UNITY_EDITOR
                        t = obj.GetType();
                        DicInspector[t]--;
#endif
                    }
                    //队列为空,从字典移除
                    if (queueCount == 0)
                    {
#if UNITY_EDITOR
                        if (null != t)
                        {
                            DicInspector.Remove(t);
                        }
#endif
                    }
                }
                //GC 整个项目中,有一处GC即可
                GC.Collect();
            }
        }
        public void Dispose()
        {
            m_dicClassObjectPool.Clear();
        }
    }
}


引用


参考文章:


1.对象池的介绍与使用_Dream_bin的博客-CSDN博客_对象池

相关文章
|
10天前
|
Web App开发 Linux C#
C# 网页截图全攻略:三种技术与 Chrome 路径查找指南
本文主要介绍了在 C# 中实现网页截图的几种技术及相关要点。涵盖了 PuppeteerSharp、Selenium 和 HtmlToImage 三种方式,分别阐述了它们的安装步骤及核心代码。同时,针对在 C# 中寻找 Windows 上 chrome.exe 路径这一问题,分析了未安装 Google Chrome 和已安装两种情况下的查找原因,并给出了相关参考链接,还列举了一系列与 C# 使用 Selenium、获取 chrome.exe 路径以及在 Linux 上部署相关的参考资料。
37 11
|
3月前
|
设计模式 IDE API
C# 一分钟浅谈:GraphQL 客户端调用
本文介绍了如何在C#中调用GraphQL API,涵盖基本步骤、常见问题及解决方案。首先,通过安装`GraphQL.Client`库并创建客户端实例,连接到GraphQL服务器。接着,展示了如何编写查询和突变,以及处理查询语法错误、变量类型不匹配等常见问题。最后,通过具体案例(如管理用户和订单)演示了如何在实际项目中应用这些技术,帮助开发者更高效地利用GraphQL。
81 38
C# 一分钟浅谈:GraphQL 客户端调用
|
3月前
|
设计模式 API 数据处理
C# 一分钟浅谈:GraphQL 客户端调用
本文介绍了如何在C#中使用`GraphQL.Client`库调用GraphQL API,涵盖基本查询、变量使用、批量请求等内容,并详细说明了常见问题及其解决方法,帮助开发者高效利用GraphQL的强大功能。
115 57
|
2月前
|
Linux C# iOS开发
开源GTKSystem.Windows.Forms框架让C# Winform支持跨平台运行
开源GTKSystem.Windows.Forms框架让C# Winform支持跨平台运行
59 12
|
2月前
|
缓存 API C#
C# 一分钟浅谈:GraphQL 优化与性能提升
本文介绍了 GraphQL API 的常见性能问题及优化方法,包括解决 N+1 查询问题、避免过度取数据、合理使用缓存及优化解析器性能,提供了 C# 实现示例。
93 33
|
2月前
|
开发框架 算法 .NET
C#/.NET/.NET Core技术前沿周刊 | 第 15 期(2024年11.25-11.30)
C#/.NET/.NET Core技术前沿周刊 | 第 15 期(2024年11.25-11.30)
|
2月前
|
开发框架 Cloud Native .NET
C#/.NET/.NET Core技术前沿周刊 | 第 16 期(2024年12.01-12.08)
C#/.NET/.NET Core技术前沿周刊 | 第 16 期(2024年12.01-12.08)
|
2月前
|
程序员 C# 数据库
C# 比较对象新思路,利用反射技术打造更灵活的比较工具
中途接手的项目,碰到需要在更新对象信息时比较并记录差异的需求,最变态的还有附加要求,怎么办?有没有既能满足需求又能对项目影响最小的方法呢?分享这个我封装的方法,一个利用反射技术打造的更灵活的比较工具
|
4月前
|
测试技术 C# 数据库
C# 单元测试框架 NUnit 一分钟浅谈
【10月更文挑战第17天】单元测试是软件开发中重要的质量保证手段,NUnit 是一个广泛使用的 .NET 单元测试框架。本文从基础到进阶介绍了 NUnit 的使用方法,包括安装、基本用法、参数化测试、异步测试等,并探讨了常见问题和易错点,旨在帮助开发者有效利用单元测试提高代码质量和开发效率。
196 64
|
3月前
|
开发框架 C# iOS开发
基于C#开源、功能强大、灵活的跨平台开发框架 - Uno Platform
基于C#开源、功能强大、灵活的跨平台开发框架 - Uno Platform