意图
放弃单独分配和释放对象,从固定池中重用对象,以提高性能和内存使用率
在游戏中,可能有这样的情况,当一个英雄释放法术,可能需要调用粒子系统,由于一次简单的释放技能就要产生成千上百的粒子(可能有这样例子,没有也先假设有),那得保证能够快速生成他们并且保证创建和销毁这些粒子不会造成内存碎片。
看上图,碎片就是在堆中的空余空间被打碎成了很多小的内存碎片,总的空间很大,但是最长的连续空间可能就会非常小,所以再次分配对象可能就会失败。当然,实际可能不会有这么小的内存,但是由于内存碎片可能会造成new 对象分配空间时为了寻找合适的连续内存而造成过多的寻址消耗。
对象池就是为了解决两个问题:
1. 减少new对象时寻址造成的消耗,消耗的原因就是内存碎片
2.二是减少Object.instantiate时内存进行的序列化和反序列化造成的CPU消耗。
使用对象池,统一进行分配和释放对象,就可以避免内存碎片的问题。同样的对象使用完后不要销毁再次进行重用,就可以减少Object.instantiate造成的消耗。
设计
public interface IPool<T>
{
T Allocate();
bool Recycle(T obj);
}
public abstract class Pool<T> : IPool<T>
{
...
protected Stack<T> mCacheStack = new Stack<T>();
...
}
public interface IObjectFactory<T>
{
T Create();
}
这里使用泛型,是为了实现一个灵活的对象池,实际情况中,肯定会有很多的资源需要用对象池来管理。
对象池实际上也就是维护一个容器,这里选择了stack来作为池的容器,也是因为在分配和释放的时候并不需要关心缓存的存储顺序,只需要要求缓存对象的地址是连续的就行了。
对象池的一个重要功能就是缓存,那么对象就要求能够在对象池内部进行创建,所以这里也抽象出一个工厂。然后,pool就变成了这样:
public abstract class Pool<T> : IPool<T>
{
#region ICountObserverable
/// <summary>
/// Gets the current count.
/// </summary>
/// <value>The current count.</value>
public int CurCount
{
get { return mCacheStack.Count; }
}
#endregion
protected IObjectFactory<T> mFactory;
protected Stack<T> mCacheStack = new Stack<T>();
/// <summary>
/// default is 5
/// </summary>
protected int mMaxCount = 5;
public virtual T Allocate()
{
return mCacheStack.Count == 0
? mFactory.Create()
: mCacheStack.Pop();
}
public abstract bool Recycle(T obj);
}
下面首先实现对象的创建器,把创建的具体逻辑用作回调,其实可以让我们在创建的时候一并完成相关的初始化等操作:
public class CustomObjectFactory<T> : IObjectFactory<T>
{
public CustomObjectFactory(Func<T> factoryMethod)
{
mFactoryMethod = factoryMethod;
}
protected Func<T> mFactoryMethod;
public T Create()
{
return mFactoryMethod();
}
}
然后是对象池,加入reset方法在用完之后把成员变量都设为初始值供下次使用:
public class SimpleObjectPool<T> : Pool<T>
{
readonly Action<T> mResetMethod;
public SimpleObjectPool(Func<T> factoryMethod, Action<T> resetMethod = null,int initCount = 0)
{
mFactory = new CustomObjectFactory<T>(factoryMethod);
mResetMethod = resetMethod;
for (int i = 0; i < initCount; i++)
{
mCacheStack.Push(mFactory.Create());
}
}
public override bool Recycle(T obj)
{
mResetMethod(obj);
mCacheStack.Push(obj);
return true;
}
}
使用:
var fishPool = new SimpleObjectPool<Fish>(() => new Fish(), null, 100);
Log.I("fishPool.CurCount:{0}", fishPool.CurCount);
var fishOne = fishPool.Allocate();
Log.I("fishPool.CurCount:{0}", fishPool.CurCount);
fishPool.Recycle(fishOne);
Log.I("fishPool.CurCount:{0}", fishPool.CurCount);
for (int i = 0; i < 10; i++)
{
fishPool.Allocate();
}
Log.I("fishPool.CurCount:{0}", fishPool.CurCount);
这里传入的匿名方法里其实就可以加上初始化之类的操作