C# · 12月 20, 2021

记一次C#的GC优化,栈和堆。

    公司游戏项目,为了热更并且减少服务器战斗逻辑计算压力,战斗过程在客户端执行,服务器有一份相同的代码,进行校验。

这时候,战斗代码的性能就显得特别重要,客户端有大量的游戏特效等等需要内存分配。每次GC都会导致客户端表现卡顿。

    使用Unity的性能分析工具Profiler分析得知有一个代码逻辑中频繁创建的对象A,占用了大量的内存,而这个对象A的生命周期又非常短。

   这个时候我就想如果这个对象A在栈上,是不是就能减少堆上的内存分配,减少GC。想分配到栈上就必须是值类型,也就只能改成结构体了。但是这个对象很大,是一个参数类型的对象,里面包含了各种参数字段有20个左右,这种对象如果改成值类型,由于值类型是复制内容副本在栈中的,这样频繁的大内存复制性能开销更大。C#本质论上对这种情况有过说明,

  由于逻辑代码也需要在客户端运行,减少GC是最重要的事情,一次GC导致的战斗表现卡顿几乎让玩家体验荡然无存,各方面考虑之后,我采用了这种方法: struct + ref 形式:

   这个对象改A成结构体,但是字段很多,可能导致频繁复制加大内存开销,为了避免这个结构体作为参数传递给方法时候复制内内容副本加大性能开销,在所有调用结构体的方法上都加上ref关键字,结构体作为引用传递(区别于类型转换,不会导致装箱拆箱),这样既不会导致装箱拆箱又不会导致内容复制,结构体只需要创建一次,占用一次存储。大大降低了内存开销。

  实测表明,对于我们项目这个对象A改成结构体后,内存分配直大大降低,跟这个对象相关的代码逻辑内存调用只有之前的1/6。