gc就是内存管理的一类算法,基本上都是通用型的大路货,鲜有专门为特定情况设计特定算法。本质上就3种算法:标识法、引用计算、内存拷贝。具体可以参考:一文搞懂七种基本的GC垃圾回收算法-腾讯云开发者社区-腾讯云 (tencent.com)
而mydog的gc算法却是针对mydog的内存布局设计的特定算法。首先我们要搞清楚,gc是为了啥?就是方便编码,程序员只需要new,不需要关心如何delete。其次,我们看看mydog内存布局:
- 其实有些数据是不需要gc的,比如表格,配置,事件等,因为它们是满格的生命周期或者是无需别人引用。
- 有些数据,特别是属性集,它们可以通过引用失效的方法而避开gc。比如角色B,C,D追着A砍,如果A下线了,那么A的属性集内存就要被回收(或释放),这时候B,C,D引用了A的某个属性集,那么当它们需要知道A的当前坐标时,发现引用失效。
- 剩下的,就是执行逻辑指令时候,可变数据大小结构的数据,就真的需要gc。
MyDog设计的gc算法,本质是延后释放+引用失效+内存池。只针对可变数据大小结构的数据进行gc。gc最核心的是垃圾回收的时机!
MyDog内存的释放(归还给池)时机分3种:(可变数据大小结构的引用,在C++中用指针实现)
- 生命周期结束,是指脚本语法中,大括号{}包括起来的临时数据,执行到}时候,那么里面所申请的数据会释放。这个和C++的临时变量一样的生命周期。和C++不一样的是,脚本里是可以打破释放时机的,比如return临时变量的引用。
- 函数体结束,有关键字new的数据,在所在的函数运行结束时进行释放(C++是整个应用周期,直到delete才释放)。同样有打破释放时机的机制。
- 数据离线,特指角色下线,那么就要释放对应的属性集。这里必须有个可以检测引用是否失效的机制。
打破释放时机:打破时机后,数据跟随释放
- 数据带到属性集里,数据成为属性集的一部分,跟随属性集释放。
- 数据存入复容器里,复容器指vector,map,set这些。
- 返回到下一个函数体里。
引用失效机制:
- 每次使用前都要获取引用数据,获取不了就要做对应处理
- 通过key对数据做记录标识,通过key查找数据
- key不能是内存地址,因为池的存在,你可能刚释放数据后,下一个指令就得到通用地址的数据,这时候的数据并不是你需要引用的数据。
- key由池地址(8bit)+记录累计次数(24bit)组成。
- 不要对高频创建的实体进行引用,比如战斗技能产生的火球,buff等。因为首次引用会产生记录,记录次数占24bit。客户端还凑合,服务器就很容易因为记录次数溢出导致错误引用。角色不属于高频,就算1s一个,也要179天后才重复。当然你也可以将key扩展成52bit,只是比较占内存而已
具体的实现是比较复杂的。
- 固定大小的数据都是在寄存器里创建,比如int,double等。而那些可变数据大小结构,比如string,vector<XXX>等,会内置一些具体的,常用的结构.具体看EPtrType。当然支持脚本定义的结构!
- 上述的内置结构都是要延后delete的,所以使用MDMemPtr统一封装起来,在寄存器有个set容器存储这些MDMemPtr。有点小纠结的是到底使用set还是unordered_set。其实小数据内,效率是差不多的,而set才占24字节,unordered_set就占80字节!最后还是选了set。
- 在编译脚本的阶段,有些时候是可以确定内置结构不需要延后delete的,所以并不是一创建内置结构的时候就要存储对应的MDMemPtr。
- 剩下的就是打破释放时机和引用失效机制。