U3D客户端框架之类对象池技术优化C#语言GC
2023-09-11 14:22:31 时间
一、类对象池概念
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();
}
}
}
引用
参考文章:
相关文章
- c#-全局键盘钩子
- C# 实现WebSocket服务端实例
- 不用Blazor WebAssembly,开发在浏览器端编译和运行C#代码的网站
- HybridCLR——划时代的Unity原生C#热更新技术
- C#订阅与发布标准实现 visual studio code .net 开发 设计模式之☞策略模式 C#字符串转二进制、二进制转字符串 c# 接口的协变和逆变 c# 使用迭代器来创建可枚举类型 博客园首页新随笔联系订阅管理 随笔 - 117 文章 - 0 评论 - 57 c# 创建,加载,修改XML文档
- XML序列化 判断是否是手机 字符操作普通帮助类 验证数据帮助类 IO帮助类 c# Lambda操作类封装 C# -- 使用反射(Reflect)获取dll文件中的类型并调用方法 C# -- 文件的压缩与解压(GZipStream)
- C# DateTime的11种构造函数 [Abp 源码分析]十五、自动审计记录 .Net 登陆的时候添加验证码 使用Topshelf开发Windows服务、记录日志 日常杂记——C#验证码 c#_生成图片式验证码 C# 利用SharpZipLib生成压缩包 Sql2012如何将远程服务器数据库及表、表结构、表数据导入本地数据库
- 反爬虫:利用ASP.NET MVC的Filter和缓存(入坑出坑) C#中缓存的使用 C#操作redis WPF 控件库——可拖动选项卡的TabControl 【Bootstrap系列】详解Bootstrap-table AutoFac event 和delegate的分别 常见的异步方式async 和 await C# Task用法 c#源码的执行过程
- 请求大神,C#如何截取字符串中指定字符之间的部分 按指定字符串分割 一分为二 c# 去除字符串中的某个已知字符
- C#不用union,而是有更好的方式实现 .net自定义错误页面实现 .net自定义错误页面实现升级篇 .net捕捉全局未处理异常的3种方式 一款很不错的FLASH时种插件 关于c#中委托使用小结 WEB网站常见受攻击方式及解决办法 判断URL是否存在 提升高并发量服务器性能解决思路
- 路由其实也可以很简单-------Asp.net WebAPI学习笔记(一) ASP.NET WebApi技术从入门到实战演练 C#面向服务WebService从入门到精通 DataTable与List<T>相互转换
- 浅谈JS中的!=、== 、!==、===的用法和区别 JS中Null与Undefined的区别 读取XML文件 获取路径的方式 C#中Cookie,Session,Application的用法与区别? c#反射 抽象工厂
- C# 字符串拼接性能探索 c#中+、string.Concat、string.Format、StringBuilder.Append四种方式进行字符串拼接时的性能
- Word控件Spire.Doc 【图像形状】教程(5) 如何在 C# 中将文本环绕在图像周围
- c# Mongodb批量更新
- c# 如何设置透明画刷
- [windows菜鸟]C#中调用Windows API的技术要点说明
- C#,入门教程(32)——程序运行时的调试技巧与逻辑错误探针技术与源代码
- [C#] Direct2D 学习笔记 (一)vb.net转换为c#
- 《圣殿祭司的ASP.NET4.0专家技术手册》----2-1 C# 4.0语言新功能
- 《圣殿祭司的ASP.NET4.0专家技术手册》----2-3 C# 4.0静态基础融入动态能力
- C#查找算法
- C#常见排序算法
- C#开发微信公众平台-就这么简单(附Demo)转载
- C#-DataTable-去重Distinct
- 一个21行C#代码实现的神经网络
- c#代码规则,C#程序中元素的命名规范
- C#与.NET Framework c#编程语言,和java是一样的。(c#,java) -->javaweb,asp.net