通常游戏开发,带gc的语言,频繁创建的小对象,都会考虑用对象池优化,从而减少gc耗时,内存碎片。但是对象池管理不当,是非常危险的,而且会增加编码难度,可能使原本易于维护的代码变得复杂化。
用户案例,用户类需要持有池对象A
的引用,进行一些操作,但池对象A
可能随时被回收掉,或者回收后再利用了,这时候再通过池对象A
的引用来使用,就会出现非常严重得错误。
上述用户案例中,池对象的生命周期可能很难控制,变得难以维护,但有时候又有这种需求,又想享受对象池带来的性能提升。
解决方案
// struct实现,避免分配堆内存
// 实现IEquatable接口,重写HashCode方法,可用于字典,避免装箱
// 重写操作符,方便使用
public struct ObjectHandle : IEquatable<ObjectHandle>
{
// 池对象维护一个ObjectID,每次生成对象时,全局自增
public Object Object { get; private set; }
public int Handle => m_CachedObjectID;
private readonly int m_CachedObjectID; // 从1开始有效,0无效
public ObjectHandle(Object Object)
{
// 创建一个Handle,记录当前对象的引用,以及ObjectID
Object = Object;
m_CachedObjectID = Object.ObjectID;
}
private bool IsValid()
{
// 对象不为空,且对象的ObjectID等于创建Handle时的ObjectID,才会认为对象可操作,否则,不应该再操作此对象了
return Object != null && m_CachedObjectID == Object.ObjectID;
}
public static implicit operator bool(ObjectHandle handle)
{
return handle.IsValid();
}
public static explicit operator Object(ObjectHandle handle)
{
return handle ? handle.Object : null;
}
public bool Equals(ObjectHandle other)
{
return CompareObjects(this, other);
}
public override bool Equals(object obj)
{
if (!(obj is ObjectHandle)) return false;
return Equals((ObjectHandle)obj);
}
public static bool operator ==(ObjectHandle lhs, ObjectHandle rhs)
{
return CompareObjects(lhs, rhs);
}
public static bool operator !=(ObjectHandle lhs, ObjectHandle rhs)
{
return !CompareObjects(lhs, rhs);
}
private static bool CompareObjects(ObjectHandle lhs, ObjectHandle rhs)
{
var validA = lhs.IsValid();
var validB = rhs.IsValid();
if (validA && !validB) return false;
if (!validA && validB) return false;
return lhs.m_CachedObjectID == rhs.m_CachedObjectID;
}
public override int GetHashCode()
{
return m_CachedObjectID;
}
}
其他
unity引擎的gc算法(BOEHM GC)过于古老,即使多了分帧机制,总gc时长并未被减少,甚至可能还增加了,使用对象池还是很有必要的。
capcom的re引擎,也是c#作为脚本,也是il2cpp技术,但是gc的效率非常高,即使ps4上每帧分配300k左右的托管堆内存,每帧gc耗时依然可以保持0.2ms左右(cedec演讲,案例是鬼泣5)。这种gc作为后盾,开发效率可以大大提高!希望unity也能达到这种效果,造福广大开发者!