通常游戏开发,带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也能达到这种效果,造福广大开发者!