Lua GC 参数的含义
lua实现了简单的incremental mark-and-sweep collector,有两个参数会影响GC的工作方式:pause和step multiplier。
概念
GC完整地清理一次内存的过程(“full gc”或”cycle”)由”标记内存是否垃圾”(mark)和”释放垃圾内存”(sweep“)两个阶段构成。一个cycle的开销通常比较大,理想情况下应该分割成多个开销较小的step。lua的GC引入一个变量debt,用以分割step,单位和内存是等比例的。如果debt是无穷大,一个step就会完成一个cycle的工作。debt<=0时,GC停止工作。分配内存时debt会等比例的增大。在mark过程中,每标记一个正在使用的object,就会根据这个object的大小从debt减去一个等比例的数值。在sweep过程中,每释放一个object,就会根据这个object的大小从debt减去一个等比例的数值。在一个cycle完成后,GC会根据当前使用的内存和pause的值计算一个新的debt。
数量关系
声明以下变量:
- TotalMemory: Lua占用的内存。
- TotalMemoryInUse : 正在被使用的对象的内存总和。
- MemoryAllocatedInMark : Mark过程中分配的内存。
- MemoryMarked : Mark过程中已标记的内存
- MemoryAllocatedInSweep: Sweep过程中分配的内存。
- MemoryFreed: 释放的对象的内存总和。
在Mark过程中,TotalMemory增加MemoryAllocatedInMark,debt的变化量是MemoryAllocatedInMark - MemoryMarked ;在Sweep过程中,TotalMemory的变化量是MemoryAllocatedInSweep - MemoryFreed,debt的变化量是 MemoryAllocatedInSweep - MemoryFreed。 在一个cycle完成之后,GC将debt的值重新设置为 TotalMemory * (1 - pause) * stepmul 内存增加时,debt也会相应的增加,step multiplier是两者的比例。
示例分析:
现象
在青柳谷中什么都不做,TotalMemory会有规律的波动,缓慢地增长到一个最大值(MemoryMax)后,快速地降低到一个最小值(MemoryBase)。设置不同的pause和set multiplier,统计数据如下:
pause(%) | stepmul(下面的值/200) | MemoryBase(M) | MemoryMax(M) | 内存增长时间(s) | 内存减少时间(s) |
---|---|---|---|---|---|
10 | 200 | 34 | 37 | 25.4 | 4.6 |
20 | 200 | 35 | 41 | 51.4 | 6.5 |
90 | 200 | 38 | 64 | 235.8 | 28.7 |
10 | 100 | 44 | 70 | 246 | 73 |
原因
在游戏中什么都不做,所以TotalMemoryInUse是基本固定的,测试数据33M。内存缓慢增长是因为每个Tick都会分配少量(大约3~4k)垃圾内存。一个cycle跑完时内存达到最小值 MemoryBase = TotalMemoryInUse + MemoryAllocatedInSweep;Mark过程中内存会持续增长,所以Mark完成时达到最大值 MemoryMax = MemoryBase + MemoryAllocatedInMark。debt累积超过34M才能完成Mark,完成前一个cycle时会设置初始debt = TotalMemory * (1 - pause) * stepmul,如果debt >= TotalMemoryInUse,GC会在下一个step完成Mark,进入Sweep;否则GC会停留在Mark阶段,等待内存分配。 以第二行数据为例,初始debt = 34 * (1 - 0.2) 即27.2M,不足以完成Mark,还缺少6.8M。内存增长到41M后,完成Mark。进入Sweep阶段后,开始释放内存,时长6.5秒,Tick分配的内存大约为 6.5* (41-35)/51.4约等于0.76M。