1. 程式人生 > >simple_pool物件池——優化

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;
        }
    }
}