simple_pool物件池——優化
一、simpe_pool物件池的問題
在上篇物件池simple_pool中提到了它現在的問題。
一個是資料控制,也就是在單個父親節點下,只會一直增加來滿足當前遊戲對物件池內的物件數量的需要,沒有考慮到減少。也就是說,若在A階段,遊戲場景中同時需要大量的某個池內O1物件,這時候就出現大量的AO1物件在記憶體中,但是過了A階段,不需要A這麼多O1物件時候,物件池內沒有做操作和優化。
另一個問題,就是多執行緒的問題。這個暫時不這裡討論。有需要可以自己先對linkedlist加鎖。
本片針對第一個問題,來做了處理。
順便給出上篇simple_pool部落格地址:
二、物件池內數量優化思路
本著在後臺處理,儘量少影響物件池的使用的原則,決定使用在新增一個執行緒來實現,對需要清理的池內物件進行記錄和判斷,然後在主執行緒中進行刪除(destroy)操作.
因為Unity不允許在其他自己建立的執行緒中呼叫Destroy函式,所以還是要在Update中進行處理。
這個是自己畫的流程圖,基本就是這個樣子。當然裡面具體引數,可以根據自己需要調整。
三、實現程式碼
1.首先,建立垃圾清理標記和垃圾物件列表。
// garbage clean.
private static bool clearGarbageFlag = false;
private static List<Transform> GarbageList = new List<Transform>(100);
然後標記為真,開始清理。這裡進行了加鎖,避免一邊刪除,一邊新增,這樣造成野的物件。垃圾表被清空了,物件也不在物件池內列表中。
2.主執行緒的清理工作
private void Update()
{
CleanGarbageList();
}
private void CleanGarbageList()
{
if (clearGarbageFlag)
{
clearGarbageFlag = false ;
lock (GarbageList)
{
Debug.Assert(GarbageList.Count > 0);
Debug.Log("now destroy " + GarbageList.Count + " from pool" + GarbageList[0].name);
for (int i = 0; i < GarbageList.Count; i++)
{
Destroy(GarbageList[i].gameObject);
}
GarbageList.Clear();
}
}
}
3.優化執行緒啟動
執行緒在第一次呼叫池的時候開始啟動。然後定時檢測數量,每輪會檢測checkTimesForEach個物件池內的未使用的情況。物件池個數超出的部分,等待5箇中的一個多次檢測沒有異常後,在加入到檢測中,這個主要是為了防止多個物件池內,一次性加入垃圾列表中太多物件,需要一次性刪掉的太多,造成主執行緒卡頓的情況。
當然這也不是最理想的,為了防止卡頓,每次檢測迴圈只檢測到一個滿足垃圾清理條件,需要處理就會停止檢測跳出迴圈,然後進行垃圾處理。這個也是為了輕量級的刪減措施。
一旦成功設定標誌,就重新計算和檢測。並且在設定後,優化執行緒等待1秒時間,來讓主執行緒做工作,這個時間應該是很充裕的。
主要程式碼
private static void OptimizationPool()
{
// check cpu time to start
Thread.Sleep(100);
// 檢測間隔時間20秒
float intervalTimeTodetect = 20f;
// after how many times counts to reset count.
// 迴圈檢測多少次,後記錄清零。每次只處理需要處理的前5個池。
const int checkTimesForEach = 5;
// 臨時池管理物件
Dictionary<int, ObjectPool> poolManagerTempDic = new Dictionary<int, ObjectPool>();
System.DateTime timeCount = System.DateTime.Now;
// 間隔時間內執行一次
bool eachMinuteGetDicOnce = false;
// 每個池未使用物件超過一半的標記,記錄次數
Dictionary<int, int> CurrentPoolUnuseCount = new Dictionary<int, int>();
// 檢測重新整理次數,也是一次計數的最大時間。
int icountLoopTime = 0;
Debug.Log("Thread start");
// 休眠時間
int sleepTime = 10;
while (isStartThread)
{
Thread.Sleep(sleepTime);
if (!eachMinuteGetDicOnce)
{
eachMinuteGetDicOnce = true;
poolManagerTempDic = poolManagerDic.ToDictionary(entry => entry.Key,entry => entry.Value);
// loop check 3 time to reset.
if (icountLoopTime % checkTimesForEach == 0)
{
CurrentPoolUnuseCount.Clear();
icountLoopTime = icountLoopTime > 10000000 ? 0 : icountLoopTime;
}
// mark unuse nuber for all.
foreach(var element in poolManagerTempDic)
{
ObjectPool opool = element.Value;
int unusinglinkCount = opool.UnusingLinkedList.Count;
// half of all is useless and more than 10.
if (unusinglinkCount * 2 > unusinglinkCount + opool.UsingLinkedList.Count && unusinglinkCount > 10)
{
MarkCountForUnusingLink(ref CurrentPoolUnuseCount, element.Key);
// satisfy the condition,add unusing link gameobject to garbagelist.
int currentMark = 0;
CurrentPoolUnuseCount.TryGetValue(element.Key,out currentMark);
// be marked three times, add to garbage list.
if (currentMark >= 3)
{
AddObjectsToGarbageList(ref opool.UnusingLinkedList);
// count tick to reset.
CurrentPoolUnuseCount[element.Key] = 0;
clearGarbageFlag = true;
// each time only gathing one pool to process.
break;
}
}
}
}
// leave time for mainthread to delete gameobjects.
if (clearGarbageFlag)
{
icountLoopTime = 0;
intervalTimeTodetect = 20f;
Thread.Sleep(1000);
timeCount = System.DateTime.Now;
}
// interval 20 seconds to start check once;
if ((System.DateTime.Now - timeCount).TotalSeconds > intervalTimeTodetect)
{
timeCount = System.DateTime.Now;
eachMinuteGetDicOnce = false;
poolManagerTempDic.Clear();
icountLoopTime++;
Debug.Log("Loop count is " + icountLoopTime);
}
// long time nothing happen, expand the detective interval time (max <= 90s).
if (icountLoopTime >= 4 )
{
intervalTimeTodetect = intervalTimeTodetect * 2 >= 90f ? 90f : intervalTimeTodetect * 2;
icountLoopTime = 0;
Debug.Log("interval time is " + intervalTimeTodetect);
}
}
return;
}
/// <summary>
/// add last gameobject to garbagelist,as when unsing unusinglink is from first place to get.
/// </summary>
/// <param name="list"></param>
private static void AddObjectsToGarbageList(ref LinkedList<Transform> list)
{
Debug.Assert(list.Count > 0);
int FlagDestroyNumber = list.Count>>1;
for (int i = 0; i < FlagDestroyNumber; i++)
{
GarbageList.Add(list.Last.Value);
list.RemoveLast();
}
}
按照目前設定引數,開始檢測的時間間隔為20秒,若20*2=40秒後,沒有需要處理的垃圾,就把檢測時間間隔翻倍為40秒檢測一次;若在過40*4=160秒,沒有觸發標誌,檢測時間進一步延長,逐次翻倍增加,但是最大值為90秒。
也就是說,最大的檢測間隔為90秒。
若中間被打斷,全部歸為正常20秒檢測一次。
觸發加入垃圾列表的條件:
設定觸發標誌是一個池,在3次檢測中都有超過一半的物件沒有被使用,並且整體未使用數量超過10個。
四、更新工程分享地址:
地址:https://github.com/cartzhang/simple_pool_bench
可以下載Assets檔案,然後用unity測試。
這個測試demo為 PoolTimeOptimizeOjbectsDemo.unity。
優化流程圖下載地址:
五、附件
poolManager.cs 全部程式碼:
using UnityEngine;
using System.Collections.Generic;
using SLQJ_POOL;
using UnityEngine.Internal;
using System.Threading;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Linq;
/// <summary>
/// 擴充套件GameObject函式
/// 使用擴充套件函式方面程式碼呼叫,可以Unity的任意程式碼中,呼叫物件池來產生和回池物件,
/// 並且不需要呼叫名稱空間等繁瑣東西。
/// 程式碼例子: obj = gameObject.InstantiateFromPool(prefab);
/// gameObject.DestroyToPool(poolObjs[poolObjs.Count - 1], 0.2f);
/// 具體可以參考HowToUse指令碼和TestEffect。
/// @cartzhang
/// </summary>
public static class GameObjectExten
{
/// <summary>
/// 呼叫物件池,產生一物件,並帶有位置和旋轉等參考
/// </summary>
/// <param name="gameobject"></param>
/// <param name="original"></param>
/// <param name="position"></param>
/// <param name="rotation"></param>
/// <returns></returns>
public static GameObject InstantiateFromPool(this GameObject gameobject, Object original, Vector3 position, Quaternion rotation)
{
return PoolManager.PullObjcetFromPool(original as GameObject, position, rotation).gameObject;
}
/// <summary>
/// 呼叫物件池,產生物件
/// </summary>
/// <param name="gameobject"></param>
/// <param name="original"></param>
/// <returns></returns>
public static GameObject InstantiateFromPool(this GameObject gameobject, Object original)
{
return PoolManager.PullObjcetFromPool(original as GameObject).gameObject;
}
/// <summary>
/// 物件返回物件池
/// </summary>
/// <param name="gameobject"></param>
/// <param name="obj"></param>
public static void DestroyToPool(this GameObject gameobject, Object obj)
{
PoolManager.PushObjectPool((obj as GameObject).transform);
}
/// <summary>
/// 帶延時的返回物件池
/// </summary>
/// <param name="gameobject"></param>
/// <param name="obj"></param>
/// <param name="t"></param>
public static void DestroyToPool(this GameObject gameobject, Object obj, [DefaultValue("0.0F")] float t)
{
PoolManager.PushObjectPool((obj as GameObject).transform, t);
}
}
namespace SLQJ_POOL
{
public class PoolManager : MonoBehaviour
{
private static PoolManager instance;
private static bool bStartThreadOnce = false;
private static bool isStartThread = false;
private static Thread tOptimizationThread;
private static List<GameObject> prefabList = new List<GameObject>();
//存放預製體對應的id,ObjcetPool
public static Dictionary<int, ObjectPool> poolManagerDic = new Dictionary<int, ObjectPool>();
private static Dictionary<Transform, ObjectPool> transformDic = new Dictionary<Transform, ObjectPool>();
// garbage clean.
private static bool clearGarbageFlag = false;
private static List<Transform> GarbageList = new List<Transform>(100);
private void Update()
{
CleanGarbageList();
}
private void CleanGarbageList()
{
if (clearGarbageFlag)
{
clearGarbageFlag = false;
lock (GarbageList)
{
Debug.Assert(GarbageList.Count > 0);
Debug.Log("now destroy " + GarbageList.Count + " from pool" + GarbageList[0].name);
for (int i = 0; i < GarbageList.Count; i++)
{
Destroy(GarbageList[i].gameObject);
}
GarbageList.Clear();
}
}
}
//初始化某個預製體對應的物件池
public static void InitPrefab(GameObject prefab, int initNum = 4)
{
GetObjectPool(prefab, initNum);
}
//外界呼叫的介面
public static Transform PullObjcetFromPool(GameObject prefab)
{
return _PullObjcetFromPool(prefab);
}
public static Transform PullObjcetFromPool(GameObject prefab, Vector3 pos, Quaternion quaternion)
{
return _PullObjcetFromPool(prefab, pos, quaternion);
}
private static Transform _PullObjcetFromPool(GameObject prefab)
{
if (prefab == null)
{
Debug.Log("prefab is null!");
return null;
}
ObjectPool objPool = GetObjectPool(prefab);
StartThreadOnce();
return objPool.PullObjcetFromPool();
}
private static Transform _PullObjcetFromPool(GameObject prefab, Vector3 pos, Quaternion quaternion)
{
if (prefab == null)
{
Debug.Log("prefab is null!");
return null;
}
ObjectPool objPool = GetObjectPool(prefab, pos, quaternion);
StartThreadOnce();
return objPool.PullObjcetFromPool(pos, quaternion);
}
private static ObjectPool GetObjectPool(GameObject prefab, int initNum = 4)
{
ObjectPool objPool = null;
//判斷集合中是否有預製體對應的物件池
int leng = prefabList.Count;
int prefabID = prefab.GetInstanceID();
for (int i = 0; i < leng; i++)
{
if (prefabID == prefabList[i].GetInstanceID())
{
objPool = poolManagerDic[prefabID];
break;
}
}
//沒有找到物件池的話建立一個物件池
if (objPool == null)
{
objPool = CreatObjcetPool(prefab, initNum);
}
return objPool;
}
private static ObjectPool GetObjectPool(GameObject prefab, Vector3 pos, Quaternion qua, int initNum = 4)
{
ObjectPool objPool = null;
int leng = prefabList.Count;
int prefabID = prefab.GetInstanceID();
for (int i = 0; i < leng; i++)
{
if (prefabID == prefabList[i].GetInstanceID())
{
objPool = poolManagerDic[prefabID];
}
}
if (objPool == null)
{
objPool = CreatObjcetPool(prefab, pos, qua, initNum);
}
return objPool;
}
private static ObjectPool CreatObjcetPool(GameObject prefab, Vector3 pos, Quaternion qua, int initNum)
{
prefabList.Add(prefab);
GameObject go = new GameObject();
go.name = prefab.name + "Pool";
ObjectPool objPool = go.AddComponent<ObjectPool>();
objPool.InitObjectPool(prefab, pos, qua, transformDic, initNum);
poolManagerDic.Add(prefab.GetInstanceID(), objPool);
return objPool;
}
private static ObjectPool CreatObjcetPool(GameObject prefab, int initNum)
{
prefabList.Add(prefab);
GameObject go = new GameObject();
go.name = prefab.name + "Pool";
ObjectPool objPool = go.AddComponent<ObjectPool>();
objPool.InitObjectPool(prefab, transformDic, initNum);
poolManagerDic.Add(prefab.GetInstanceID(), objPool);
return objPool;
}
public static void PushObjectPool(Transform handleTransform)
{
ObjectPool objPool = GetPoolByTransform(handleTransform);
if (objPool)
{
objPool.PushObjectToPool(handleTransform);
}
else
{
GameObject.Destroy(handleTransform.gameObject);
}
}
public static void PushObjectPool(Transform handleTransform, float delayTime)
{
ObjectPool objPool = GetPoolByTransform(handleTransform);
if (objPool)
{
objPool.PushObjectToPool(handleTransform, delayTime);
}
else
{
GameObject.Destroy(handleTransform.gameObject, delayTime);
}
}
//立即回池的介面
public static void PushObjectPool(Transform handleTransform, GameObject prefab)
{
ObjectPool objPool = GetObjectPool(prefab);
objPool.PushObjectToPool(handleTransform.transform);
}
//延遲迴池的介面
public static void PushObjectPool(Transform handleTransform, GameObject prefab, float delayTime)
{
ObjectPool objPool = GetObjectPool(prefab);
objPool.PushObjectToPool(handleTransform, delayTime);
}
private static ObjectPool GetPoolByTransform(Transform handleTransform)
{
if (transformDic.ContainsKey(handleTransform))
{
return transformDic[handleTransform];
}
Debug.LogError(handleTransform.name + " no find it's ObjectPool");
return null;
}
// add code to clean pool from time to time.
private static void StartThreadOnce()
{
// start thread to clean pool from time to time.
if (!bStartThreadOnce)
{
bStartThreadOnce = true;
ThreadPool.QueueUserWorkItem(AutoToCheckOptimization);
}
}
private static void AutoToCheckOptimization(object obj)
{
Thread.Sleep(10);
isStartThread = true;
tOptimizationThread = new Thread(OptimizationPool);
tOptimizationThread.Start();
}
private static void OptimizationPool()
{
// check cpu time to start
Thread.Sleep(100);
// 檢測間隔時間20秒
float intervalTimeTodetect = 20f;
// after how many times counts to reset count.
// 迴圈檢測多少次,後記錄清零。每次只處理需要處理的前5個池。
const int checkTimesForEach = 5;
// 臨時池管理物件
Dictionary<int, ObjectPool> poolManagerTempDic = new Dictionary<int, ObjectPool>();
System.DateTime timeCount = System.DateTime.Now;
// 間隔時間內執行一次
bool eachMinuteGetDicOnce = false;
// 每個池未使用物件超過一半的標記,記錄次數
Dictionary<int, int> CurrentPoolUnuseCount = new Dictionary<int, int>();
// 檢測重新整理次數,也是一次計數的最大時間。
int icountLoopTime = 0;
Debug.Log("Thread start");
// 休眠時間
int sleepTime = 10;
while (isStartThread)
{
Thread.Sleep(sleepTime);
if (!eachMinuteGetDicOnce)
{
eachMinuteGetDicOnce = true;
poolManagerTempDic = poolManagerDic.ToDictionary(entry => entry.Key,entry => entry.Value);
// loop check 3 time to reset.
if (icountLoopTime % checkTimesForEach == 0)
{
CurrentPoolUnuseCount.Clear();
icountLoopTime = icountLoopTime > 10000000 ? 0 : icountLoopTime;
}
// mark unuse nuber for all.
foreach(var element in poolManagerTempDic)
{
ObjectPool opool = element.Value;
int unusinglinkCount = opool.UnusingLinkedList.Count;
// half of all is useless and more than 10.
if (unusinglinkCount * 2 > unusinglinkCount + opool.UsingLinkedList.Count && unusinglinkCount > 10)
{
MarkCountForUnusingLink(ref CurrentPoolUnuseCount, element.Key);
// satisfy the condition,add unusing link gameobject to garbagelist.
int currentMark = 0;
CurrentPoolUnuseCount.TryGetValue(element.Key,out currentMark);
// be marked three times, add to garbage list.
if (currentMark >= 3)
{
AddObjectsToGarbageList(ref opool.UnusingLinkedList);
// count tick to reset.
CurrentPoolUnuseCount[element.Key] = 0;
clearGarbageFlag = true;
// each time only gathing one pool to process.
break;
}
}
}
}
// leave time for mainthread to delete gameobjects.
if (clearGarbageFlag)
{
icountLoopTime = 0;
intervalTimeTodetect = 20f;
Thread.Sleep(1000);
timeCount = System.DateTime.Now;
}
// interval 20 seconds to start check once;
if ((System.DateTime.Now - timeCount).TotalSeconds > intervalTimeTodetect)
{
timeCount = System.DateTime.Now;
eachMinuteGetDicOnce = false;
poolManagerTempDic.Clear();
icountLoopTime++;
Debug.Log("Loop count is " + icountLoopTime);
}
// long time nothing happen, expand the detective interval time (max <= 90s).
if (icountLoopTime >= 4 )
{
intervalTimeTodetect = intervalTimeTodetect * 2 >= 90f ? 90f : intervalTimeTodetect * 2;
icountLoopTime = 0;
Debug.Log("interval time is " + intervalTimeTodetect);
}
}
return;
}
private static void MarkCountForUnusingLink(ref Dictionary<int, int> poolUnuseCount,int prefabGuid)
{
Debug.Assert(null != poolManagerDic);
int currentMark = 0;
if (poolUnuseCount.ContainsKey(prefabGuid))
{
poolUnuseCount.TryGetValue(prefabGuid, out currentMark);
}
currentMark++;
if (poolUnuseCount.ContainsKey(prefabGuid))
{
poolUnuseCount[prefabGuid] = currentMark;
}
else
{
poolUnuseCount.Add(prefabGuid, currentMark);
}
}
/// <summary>
/// add last gameobject to garbagelist,as when unsing unusinglink is from first place to get.
/// </summary>
/// <param name="list"></param>
private static void AddObjectsToGarbageList(ref LinkedList<Transform> list)
{
Debug.Assert(list.Count > 0);
int FlagDestroyNumber = list.Count>>1;
for (int i = 0; i < FlagDestroyNumber; i++)
{
GarbageList.Add(list.Last.Value);
list.RemoveLast();
}
}
public void Dispose()
{
isStartThread = false;
}
}
}