对象池(ObjectPool)

什么是对象池?

池(Pool),与集合在某种意义上有些相似。 水池,是一定数量的水的集合;内存池,是一定数量的已经分配好的内存的集合;线程池,是一定数量的已经创建好的线程的集合。那么,对象池,顾名思义就是一定数量的已经创建好的对象(Object)的集合。

对象池是干什么的?

先来说说内存碎片

内存碎片一般是由于空闲的连续空间比要申请的空间小,导致这些小内存块不能被利用。产生内存碎片的方法很简单,举个例子:

假设我们有十四个空余字节,但是被一块正在使用的内存分割成了两个七字节的碎片。 而我们尝试分配十二字节的对象,那么就会失败。

移动设备编程在许多方面比传统的PC编程更接近嵌入式编程,内存稀缺,用户期望游戏稳定,高效的压缩内存管理器很少可用。 在这种环境中,内存碎片是致命的。
碎片意味着在堆中的空余空间被打碎成了很多小的内存碎片,而不是大的连续内存块。 总共的可用内存也许很大,但是最长的连续空间可能难以忍受地小。
哪怕碎片化发生得不频繁,它仍会逐渐把堆变成有空洞和裂隙的不可用空间,最终无法运行游戏。

这有点像停了很多车的繁忙街道。 如果它们挤在一起,尽管空间还是有剩余的,但空闲地带变成了车之间的碎片空间。

对象池的作用

在C/C++的程序中,如果一种对象,你要经常用malloc/free(或new/delete)来创建、销毁,这样子一方面开销会比较大,另一方面会产生很多内存碎片,程序跑的时间一长,性能就会下降。这个时候,就产生了对象池。可以事先创建好一批对象,放在一个集合中,以后每当程序需要新的对象时候,都从对象池里获取,程序用完该对象后,再把该对象归还给对象池。这样,就会少了很多的malloc/free(new/delete)的调用,在一定程度上提高了系统的性能,尤其在动态内存分配比较频繁的程序中效果较为明显。

关于多线程的考虑

因为Unity的API只能被主线程调用,所以我们不需要将对象池实现支持多线程。在支持多线程的应用中,单例的初始化通常要加一个锁,在这里也没有必要。

怎样实现一个对象池?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
using System;
using System.Collections.Generic;

public class ObjectPool<T>
{
private const int DefaultMaxCount = 10000;
private readonly Stack<T> mStack = new Stack<T>();
//创建对象方法
private readonly Func<T> mFuncOnCreate;
//获取对象成功事件
private readonly Action<T> mActionOnGet;
//释放对象成功事件
private readonly Action<T> mActionOnRelease;
private int mMaxCount;
public int CurCount
{
get { return mStack.Count; }
}

public ObjectPool(Func<T> funcOnCreate, Action<T> actionOnGet, Action<T> actionOnRelease) : this(DefaultMaxCount, funcOnCreate, actionOnGet, actionOnRelease)
{

}

public ObjectPool(int maxCount, Func<T> funcOnCreate, Action<T> actionOnGet, Action<T> actionOnRelease)
{
mMaxCount = maxCount;
mFuncOnCreate = funcOnCreate;
mActionOnGet = actionOnGet;
mActionOnRelease = actionOnRelease;
}

/// <summary>
/// 取出一个对象
/// </summary>
/// <returns></returns>
public T Get()
{
T element = mStack.Count > 0 ? mStack.Pop() : mFuncOnCreate();
mActionOnGet?.Invoke(element);
return element;
}

/// <summary>
/// 回收一个对象
/// </summary>
/// <param name="element"></param>
/// <returns></returns>
public bool Release(T element)
{
//安全检验
if (mStack.Count > 0 && ReferenceEquals(mStack.Peek(), element))
{
return false;
}

//对象池容量检验
if (mStack.Count >= mMaxCount)
{
return false;
}

mActionOnRelease?.Invoke(element);
mStack.Push(element);
return true;
}
}

参考

  1. 对象池模式
  2. 内存碎片