池化思想 内存管理 减GC

在游戏开发当中,GC是一个重要的优化项,Unity厂家建议每帧别超过大概50B(大概这个数量级,我也记不清了),但是对于复杂点的游戏,各种Entity逻辑,动态结构,如果不进行优化的很难做到这个级别。下面提出一种池化思想,通过进行内存管理,来减少GC。中心思想是把释放的东西保存起来管理,要用时直接取出使用。
下面直接来看代码:

public abstract class PoolObject
{
    public IObjectPool _pool;
    public int InstanceId;
    public bool isInPool;
    public Action InitAction;
    public Action ReleaseAction;

    public void Release()
    {
        _pool?.CollectObject(this);
    }
}

PoolObject是一个抽象类,程序中需要实现池化管理的类需要继承这个抽象类,类中有两个委托,分别是对象创建和释放时的回调。

public interface IObjectPool
{
    void CollectObject(PoolObject poolObject);
    void Clear();
 }
public class ObjectPool : IObjectPool where T : PoolObject, new()
{
    private static ObjectPool pool;
    private static List m_PooledObjList = new List();
    public static T Alloc()
    {
        if (pool == null)
        {
            pool = new ObjectPool();
            ObjectPoolManager.AddPool(pool);
        }
        T poolObject;
        if (m_PooledObjList.Count == 0)
        {
            poolObject = new T()
            {
                _pool = pool,
            };
        }
        else
        {
            var count = m_PooledObjList.Count;
            poolObject = m_PooledObjList[count - 1];
            m_PooledObjList.RemoveAt(count - 1);
        }

        poolObject.isInPool = false;
        poolObject.InstanceId = ObjectPoolManager.NextInstanceId;
        poolObject.InitAction?.Invoke();
        return poolObject;
    }
    public void CollectObject(PoolObject poolObject)
    {
        poolObject.isInPool = true;
        poolObject.InstanceId = 0;
        poolObject.ReleaseAction?.Invoke();
        m_PooledObjList.Add((T)poolObject);
    }
    public void Clear()
    {
        m_PooledObjList.Clear();
    }
}

这个是资源池,有一个静态的List会去缓存释放的对象,当需要时再取出。如果数量不够就直接创建。这块的化也可以做一些优化,比如初始化的时候就创建一定的数量。每一个PoolObject会引用到Pool,释放时会到Pool中执行相应的操作。

public struct PoolObjectRef where T : PoolObject
{
    private T m_poolObject;
    public int m_instanceId;

    public T PoolObject{
        get
        {
            if (m_poolObject == null || m_instanceId != m_poolObject.InstanceId)
            {
                return null;
            }
            return m_poolObject;
        }
    }

    public PoolObjectRef(T poolObject)
    {
        m_poolObject = poolObject;
        m_instanceId = poolObject.InstanceId;
    }

    public bool IsNull()
    {
        return m_poolObject == null || m_instanceId != m_poolObject.InstanceId;
    }

    public bool IsRef(T p)
    {
        return m_poolObject == p && m_instanceId == p.InstanceId;
    }

    public void ReleaseRef()
    {
        m_poolObject = null;
        m_instanceId = 0;
    }
}

这是一个资源引用的管理,你可能在某一个地方引用到一个对象,但是在一个时刻对象被释放收集到资源池中,正常语法你保持这这个引用还能去访问到释放的对象,这就影响到了这个对象使用上的生命周期,按设计他不能再被使用,于是这边利用InstanceId来判断是否还保持着原对象的引用,如果对象被释放,这边也就不再保持引用。使用之前判断一下IsNull.

public static class ObjectPoolManager
{
    private static List m_poolList = new List();
    public static void AddPool(IObjectPool pool)
    {
        m_poolList.Add(pool);
    }

    public static void ClearPool()
    {
        foreach (var pool in m_poolList)
        {
            pool.Clear();
        }
        m_poolList.Clear();
    }

    private static int curInstanceId;

    public static int NextInstanceId
    {
        get
        {
            curInstanceId++;
            return curInstanceId;
        }
    }
}

这部分是资源池,把所有池里的资源都存在这里,统一管理。InstanceId是给所有PoolObject的一个标识,能够用来判断是否还存在引用关系。

下面给点测试代码
//先来一个Entity类

public class Entity : PoolObject
{
    private static int Index = 0;
    public int CreatedIndex;
    public Entity()
    {
        InitAction = () =>
        {
            Console.WriteLine("Entity Inited");
        };
        ReleaseAction = () =>
        {
            Console.WriteLine("Entity Released");
        };
        Index++;
        CreatedIndex = Index;
        Console.WriteLine($"{Index} Entity Created");
    }
}

Test1:

     var list = new List();
     for (var index = 0; index < 10; index++)
     {
         list.Add(ObjectPool.Alloc());
     }

     for (var index = 9; index >= 5; index--)
     {
         list[index].Release();
         list.RemoveAt(index);
     }

     for (var index = 0; index < 10; index++)
     {
         list.Add(ObjectPool.Alloc());
     }

先从资源池里申请10个Entity,再释放5个,再申请10个,我们可以看到Entity总共被创建了15个,说明资源池起作用了。不然这种情况Entity是会创建20次。

Test2:

    var entityList = new List();
    for (var i = 0; i < 15; i++) entityList.Add(ObjectPool.Alloc());
    var entityRefList1 = new List>();
    for (var i = 0; i < 10; i++) entityRefList1.Add(new PoolObjectRef(entityList[i]));

    var entityRefList2 = new List>();
    for (var i = 5; i < 15; i++) entityRefList2.Add(new PoolObjectRef(entityList[i]));

    foreach (var entityRef in entityRefList1)
    {
        if (!entityRef.IsNull())
        {
            if (entityRef.PoolObject is Entity entity) Console.WriteLine($"EntityRef Object Index {entity.CreatedIndex}");
        }
        else
        {
            Console.WriteLine($"EntityRef Object Is Null");
        }
    }

    for (int i = 12; i >= 3; i--)
    {
        entityList[i].Release();
        entityList.RemoveAt(i);
    }
    
    
    foreach (var entityRef in entityRefList1)
    {
        if (!entityRef.IsNull())
        {
            if (entityRef.PoolObject is Entity entity) Console.WriteLine($"EntityRef Object Index {entity.CreatedIndex}");
        }
        else
        {
            Console.WriteLine($"EntityRef Object Is Null");
        }
    }
    
    
    foreach (var entityRef in entityRefList2)
    {
        if (!entityRef.IsNull())
        {
            if (entityRef.PoolObject is Entity entity) Console.WriteLine($"EntityRef Object Index {entity.CreatedIndex}");
        }
        else
        {
            Console.WriteLine($"EntityRef Object Is Null");
        }
    }

第二个测试用例中试了下PoolObjectRef的作用。我们先创建15个Entity的数组,再创建两个PoolObjectRef的数组,一个引用前10个Entity,一个引用后10个,遍历第一个ref数组,能正常打印出所有所有数据,当我们把所以3-12的Entity放回资源池中,我们可以再遍历两个ref数组,发现第一个数组只有前3个还保持着引用,后一个数组只有最后两个还保持引用。和预期相符合。

总结:利用资源池能将程序中的GC降低到一个很低的程度,当然还有一些其它手段来降低GC。这种思想的副作用是会增大程序的内存使用,这就需要权衡一下了,看看项目对内存敏感不。

有问题或者优化思路的可以一起讨论一下。

你可能感兴趣的:(游戏开发)