【Unity優(yōu)化】構(gòu)建一個(gè)拒絕GC的List

版權(quán)聲明:本文為博主原創(chuàng)文章,歡迎轉(zhuǎn)載。請(qǐng)保留博主鏈接:http://blog.csdn.net/andrewfan

上篇文章《【Unity優(yōu)化】Unity中究竟能不能使用foreach?》發(fā)表之后,曾經(jīng)有網(wǎng)友說(shuō),在他的不同的Unity版本上,發(fā)現(xiàn)了泛型List無(wú)論使用foreach還是GetEnumerator均會(huì)產(chǎn)生GC的情況,這就有點(diǎn)尷尬了。由于它本身就是Mono編譯器和相應(yīng).net庫(kù)才能決定的原因,這就使得在使用系統(tǒng)提供的List時(shí),又能最終擺脫GC的糾纏變得很困難。于是抓耳撓腮,翻出差不多六七年為Java代碼寫(xiě)的動(dòng)態(tài)數(shù)組,然后大肆修改一番。最終好像終于逃離GC的魔咒。

先奉上代碼:

自定義的List

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


namespace AndrewBox.Math
{
    /// <summary>
    ///  動(dòng)態(tài)數(shù)組
    ///  @author AndrewFan
    /// </summary>
    /// <typeparam name="T">任意類型</typeparam>
    public class AB_List<T> :IEnumerable<T>
    {
        protected int m_capacity=10;  // 容量
        protected T[] m_items;// 內(nèi)部數(shù)組
        protected int m_length;// 存放的單元個(gè)數(shù)
        protected int m_mayIdleID;// 可能空閑的單元下標(biāo)

        protected IEnumerator<T>[] m_enumerators; //枚舉器組
        protected bool[] m_enumStates;//枚舉器組當(dāng)前占用狀態(tài)
        public AB_List()
        {
            init(5);
        }
        public AB_List(int capacity,int enumCount=5)
        {
            init(enumCount);
        }
        protected void init(int enumCount)
        {
            m_capacity = m_capacity<10?10:m_capacity;
            enumCount = enumCount < 5 ? 5 : enumCount;
            m_items = new T[m_capacity];
            if (m_enumerators == null)
            {
                m_enumerators = new IEnumerator<T>[enumCount];
                m_enumStates = new bool[enumCount];
                for (int i = 0; i < m_enumerators.Length; i++)
                {
                    m_enumerators[i] = new ABEnumerator<T>(this,i);
                }
            }
        }
        /// <summary>
        /// 增加單元
        /// </summary>
        /// <param name="element">添加的單元</param>
        public virtual void Add(T element)
        {
            increaseCapacity();
            // 賦值
            m_items[m_length] = element;
            m_length++;
        }

        /// <summary>
        /// 插入單元
        /// </summary>
        /// <param name="index">插入位置</param>
        /// <param name="element">單元</param>
        /// <returns>操作是否成功</returns>
        public virtual bool Insert(int index, T element)
        {
            if (index < 0)
            {
                return false;
            }
            if (index >= m_length)
            {
                Add(element);
                return true;
            }
            increaseCapacity();
            // 向后拷貝
            // for(int i=length;i>index;i--)
            // {
            // datas[i]=datas[i-1];
            // }
            System.Array.Copy(m_items, index, m_items, index + 1, m_length - index);

            m_items[index] = element;

            m_length++;
            return true;
        }

        public virtual T this[int index]
        {
            get
            {
                //取位于某個(gè)位置的單元
                if (index < 0 || index >= m_length)
                {
                    throw new InvalidOperationException();
                }
                return m_items[index];
            }
            set
            {
                //設(shè)置位于某個(gè)位置的單元
                if (index < 0 || index >= m_length)
                {
                    throw new InvalidOperationException();
                }
                m_items[index] = value;
            }
        }

        /// <summary>
        /// 增長(zhǎng)容量
        /// </summary>
        protected void increaseCapacity()
        {
            if (m_length >= m_capacity)
            {
                int newCapacity = m_capacity;
                if(newCapacity == 0)
                {
                    newCapacity++;
                }
                newCapacity *= 2;
                T[] datasNew = new T[newCapacity];
                System.Array.Copy(m_items, 0, datasNew, 0, m_length);
                m_items = datasNew;
                m_capacity = newCapacity;
            }
        }
        /// <summary>
        /// 清空單元數(shù)組
        /// </summary>
        public virtual void Clear()
        {
            for (int i = 0; i < m_length; i++)
            {
                m_items[i] = default(T);
            }
            m_length = 0;
        }


        /// <summary>
        /// 是否包含某個(gè)單元
        /// </summary>
        /// <param name="element">單元</param>
        /// <returns>是否包含</returns>
        public bool Contains(T element)
        {
            for (int i = 0; i < m_length; i++)
            {
                if (m_items[i].Equals(element))
                {
                    return true;
                }
            }
            return false;
        }
  

        /// <summary>
        /// 獲取指定單元在當(dāng)前列表中的位置,從前向后查找
        /// </summary>
        /// <param name="element">單元</param>
        /// <returns>位置</returns>
        public int IndexOf(T element)
        {
            for (int i = 0; i < m_length; i++)
            {
                if (m_items[i].Equals(element))
                {
                    return i;
                }
            }
            return -1;
        }
        /// <summary>
        /// 獲取指定單元在當(dāng)前列表中的位置,從后先前查找
        /// </summary>
        /// <param name="element">單元</param>
        /// <returns>位置</returns>
        public int LastIndexOf(T element)
        {
            for (int i = m_length-1; i >=0; i--)
            {
                if (m_items[i].Equals(element))
                {
                    return i;
                }
            }
            return -1;
        }

        /// <summary>
        /// 獲得長(zhǎng)度
        /// </summary>
        public virtual int Count
        {
            get
            {
                return m_length;
            }
        }
        /// <summary>
        /// 移除指定位置的單元,如果單元?dú)w屬權(quán)屬于當(dāng)前列表,則會(huì)將其卸載
        /// </summary>
        /// <param name="index">位置索引</param>
        /// <returns>移除掉的單元</returns>
        public virtual void RemoveAt(int index)
        {
            if (index < 0 || index >= m_length)
            {
                return;
            }
            for (int i = index; i <= m_length - 2; i++)
            {
                m_items[i] = m_items[i + 1];
            }
            m_length--;
        }
        /// <summary>
        /// 移除指定尾部單元
        /// </summary>
        /// <returns>移除掉的單元</returns>
        public virtual T RemoveEnd()
        {
            if (m_length <= 0)
            {
                return default(T);
            }
            T temp = m_items[m_length - 1];
            m_items[m_length - 1] = default(T);
            m_length--;
            return temp;
        }
        /// <summary>
        /// 從指定位置開(kāi)始(包括當(dāng)前),移除后續(xù)單元,如果單元?dú)w屬權(quán)屬于當(dāng)前列表,則會(huì)將其卸載
        /// </summary>
        /// <param name="index">要移除的位置</param>
        /// <param name="innerMove">是否是內(nèi)部移動(dòng)</param>
        /// <returns>被移除的個(gè)數(shù),如果index越界,則返回-1</returns>
        public virtual int RemoveAllFrom(int index)
        {
            if (index < 0 || index >= m_length)
            {
                return -1;
            }
            int removedNum = 0;
            for (int i = m_length - 1; i >= index; i--)
            {
                m_items[i] = default(T);
                m_length--;
                removedNum++;
            }
            return removedNum;
        }
        /// <summary>
        /// 移除指定單元,如果單元?dú)w屬權(quán)屬于當(dāng)前列表,則會(huì)將其卸載
        /// </summary>
        /// <param name="element">單元</param>
        /// <returns>是否操作成功</returns>
        public virtual bool Remove(T element)
        {
            int index = IndexOf(element);
            if (index < 0)
            {
                return false;
            }
            RemoveAt(index);
            return true;
        }
        /// <summary>
        /// 獲取所有數(shù)據(jù),注意這里的數(shù)據(jù)可能包含了很多冗余空數(shù)據(jù),長(zhǎng)度>=當(dāng)前數(shù)組長(zhǎng)度。
        /// </summary>
        /// <returns>所有數(shù)據(jù)數(shù)組</returns>
        public T[] GetAllItems()
        {
            return m_items;
        }
        /// <summary>
        /// 轉(zhuǎn)換成定長(zhǎng)數(shù)組,伴隨著內(nèi)容拷貝。
        /// 如果是值類型數(shù)組,將與本動(dòng)態(tài)數(shù)組失去關(guān)聯(lián);
        /// 如果是引用類型數(shù)組,將與本動(dòng)態(tài)數(shù)組保存相同的引用。
        /// </summary>
        /// <returns>數(shù)組</returns>
        public virtual Array ToArray()
        {
            T[] array = new T[m_length];
            for (int i = 0; i < m_length; i++)
            {
                array[i] = m_items[i];
            }
            return array;
        }
        /// <summary>
        /// 顯示此數(shù)組,每個(gè)單元之間以逗號(hào)分隔
        /// </summary>
        public void Show()
        {
            string text = "";
            for (int i = 0; i < m_length; i++)
            {
                T obj = m_items[i];
                text += (obj.ToString() + ",");
            }
            Debug.Log(text);
        }

        /// <summary>
        /// 顯示此數(shù)組,每個(gè)單元一行
        /// </summary>
        public void ShowByLines()
        {
            string text = "";
            for (int i = 0; i < m_length; i++)
            {
                T obj = m_items[i];
                text += (obj.ToString());
            }
            Debug.Log(text);
        }


        public IEnumerator<T> GetEnumerator()
        {
            //搜索可用的枚舉器
            int idleEnumID = -1;
            for (int i = 0; i < m_enumStates.Length; i++)
            {
                int tryID=i+m_mayIdleID;
                if (!m_enumStates[tryID])
                {
                    idleEnumID = tryID;
                    break;
                }
            }
            if (idleEnumID < 0)
            {
                    Debug.LogError("use too much enumerators");
            }
            //標(biāo)記為已經(jīng)使用狀態(tài)
            m_enumStates[idleEnumID] = true;
            m_enumerators[idleEnumID].Reset();
            //向前遷移空閑坐標(biāo)
            m_mayIdleID = (m_mayIdleID + 1) % m_enumStates.Length;
            return m_enumerators[idleEnumID];
        }
        IEnumerator  IEnumerable.GetEnumerator()
        {
            return null;
        }

        struct ABEnumerator<T> : IDisposable, IEnumerator<T>
        {
            private AB_List<T> m_list;
            private int m_idNext;
            private T m_current;
            private int m_id;
           public  object Current
            {
                get
                {
                    if (this.m_idNext <= 0)
                    {
                        throw new InvalidOperationException();
                    }
                    return this.m_current;
                }
            }
            T IEnumerator<T>.Current
            {
                get
                {
                    return this.m_current;
                }
            }

            internal ABEnumerator(AB_List<T> list,int id)
            {
                this.m_list = list;
                this.m_idNext = 0;
                this.m_id=id;
                m_current = default(T);
            }

            void IEnumerator.Reset()
            {
                this.m_idNext = 0;
            }

            public void Dispose()
            {
                //this.m_list = null;
                //清除使用標(biāo)記
                m_list.m_enumStates[m_id] = false;
                m_list.m_mayIdleID = m_id;
            }


            public bool MoveNext()
            {
                if (this.m_list == null)
                {
                    throw new ObjectDisposedException(base.GetType().FullName);
                }
                if (this.m_idNext < 0)
                {
                    return false;
                }
                if (this.m_idNext < this.m_list.Count)
                {
                    this.m_current = this.m_list.m_items[this.m_idNext++];
                    return true;
                }
                this.m_idNext = -1;
                return false;
            }
        }
    }

   

}



下面是修改后的ForeachTest 類

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using AndrewBox.Math;

public class ForeachTest : MonoBehaviour {

    int[] m_intArray;
    List<int> m_intList;
    ArrayList m_arryList;
    AB_List<int> m_intABList;
    public void Start () 
    {
        m_intArray = new int[2];
        m_intList = new List<int>();
        m_arryList = new ArrayList();
        m_intABList = new AB_List<int>();
        for (int i = 0; i < m_intArray.Length; i++)
        {
            m_intArray[i] = i;
            m_intList.Add(i);
            m_arryList.Add(i);
            m_intABList.Add(i);
        }
    }

    void Update () 
    {
        testABListEnumLevel();
        //testABListGetEmulator();
        //testABListForeach();
    }

    void testIntListForeach()
    {
        for (int i = 0; i < 1000; i++)
        {
            foreach (var iNum in m_intList)
            {
            }
        }
    }
    void testIntListGetEmulator()
    {
        for (int i = 0; i < 1000; i++)
        {
            var iNum = m_intList.GetEnumerator();
            while (iNum.MoveNext())
            {
            }
        }
    }
    void testIntArrayForeach()
    {
        for (int i = 0; i < 1000; i++)
        {
            foreach (var iNum in m_intArray)
            {
            }
        }
    }
    void testIntArrayGetEmulator()
    {
        for (int i = 0; i < 1000; i++)
        {
            var iNum = m_intArray.GetEnumerator();
            while (iNum.MoveNext())
            {
            }
        }
    }
    void testArrayListForeach()
    {
        for (int i = 0; i < 1000; i++)
        {
            foreach (var iNum in m_arryList)
            {
            }
        }
    }
    void testArrayListGetEmulator()
    {
        for (int i = 0; i < 1000; i++)
        {
            var iNum = m_arryList.GetEnumerator();
            while (iNum.MoveNext())
            {
            }
        }
    }
    void testABListForeach()
    {
        for (int i = 0; i < 1000; i++)
        {
            foreach (var iNum in m_intABList)
            {
            }
        }
    }
    void testABListGetEmulator()
    {
        for (int i = 0; i < 1000; i++)
        {
            using (var iNum = m_intABList.GetEnumerator())
            {
                while (iNum.MoveNext())
                {
                    var t = iNum.Current;
                }
            }
        }
    }
    void testABListEnumLevel()
    {
        foreach (var iNum1 in m_intABList)
        {
            foreach (var iNum2 in m_intABList)
            {
                foreach (var iNum3 in m_intABList)
                {
                    foreach (var iNum4 in m_intABList)
                    {
                        //foreach (var iNum5 in m_intABList)
                        //{
                        //}
                    }
                }
            }
        }
    }
}

Foreach調(diào)用解析

關(guān)鍵之處作個(gè)解釋:
首先理清楚IEnumerable、IEnumerator之間的關(guān)系。
IEnumerable是指那種可以被枚舉的列表類型,如果我們自己自定義一個(gè)List,希望它能結(jié)合foreach使用的話,必須實(shí)現(xiàn)這個(gè)接口。
IEnumerator是一個(gè)枚舉器。

系統(tǒng)庫(kù)里的IEnumerable接口是這樣:

using System.Runtime.InteropServices;

namespace System.Collections
{
    public interface IEnumerable
    {
        IEnumerator GetEnumerator();
    }
}

在我的ABList類中實(shí)現(xiàn)接口的函數(shù)是下面這樣:

        IEnumerator  IEnumerable.GetEnumerator()
        {
            if (m_enumerator == null)
            {
                m_enumerator = new ABEnumerator<T>(this);
            }
            m_enumerator.Reset();
            return m_enumerator;
        }

目前的函數(shù)實(shí)現(xiàn)經(jīng)過(guò)設(shè)計(jì)的話,它不會(huì)產(chǎn)生GC。然而,問(wèn)題在后面緊緊跟隨。實(shí)現(xiàn)了IEnumerable接口之后。當(dāng)我們使用形如foreach(var t in list)的時(shí)刻,它就會(huì)去調(diào)用list中的繼承于IEnumerator的Current實(shí)現(xiàn):

namespace System.Collections
{
    public interface IEnumerator
    {
        object Current
        {
            get;
        }

        bool MoveNext();
        void Reset();
    }
}

看到這里,它返回的是object,如果我們List中存放的是值類型,那么系統(tǒng)自然就產(chǎn)生了一次box裝箱操作,GC于是悄悄地產(chǎn)生了。
也正是因?yàn)檫@個(gè)原因,微軟后來(lái)加入了泛型的IEnumerator。但是,為了兼容以前的設(shè)計(jì),這個(gè)泛型IEnumerator被設(shè)計(jì)成實(shí)現(xiàn)于之前的IEnumerator,而它的下方增加了同樣的Current的Get方法。

using System;
using System.Collections;

namespace System.Collections.Generic
{
    public interface IEnumerator<T> : IEnumerator, IDisposable
    {
        T Current
        {
            get;
        }
    }
}

同樣的設(shè)計(jì)也被用于泛型的IEnumerable,

using System.Collections;

namespace System.Collections.Generic
{
    public interface IEnumerable<T> : IEnumerable
    {
        IEnumerator<T> GetEnumerator();
    }
}

如果我們實(shí)現(xiàn)泛型的IEnumerable和IEnumerator,必須同時(shí)泛型和非泛型的GetEnumerator和Current方法。
那么,問(wèn)題來(lái)了?,F(xiàn)在有兩個(gè)GetEnumerator()方法,兩個(gè)Current的Get方法,究竟該用誰(shuí)的呢?
首先,在實(shí)現(xiàn)的時(shí)候就需要加以區(qū)分:

        public IEnumerator<T> GetEnumerator()
        IEnumerator  IEnumerable.GetEnumerator()

這兩個(gè)實(shí)現(xiàn),你給其中一個(gè)加上public,另外一個(gè)就不能加上public;兩個(gè)函數(shù)至少有一個(gè)需要增加[接口名稱.]這種前綴;那么最終我們?cè)趂oreach期間調(diào)用的就是public的那個(gè)方法。
自然,我們這里為了避免使用到非泛型IEnumerator中的Current方法的object返回形式,我們必須使用將唯一的生存權(quán)留給泛型的GetEnumerator。
同樣,我們也需要在自定義的枚舉器中作出選擇。保留泛型的Current函數(shù)。

 struct ABEnumerator<T> : IDisposable, IEnumerator<T>
 {
         private AB_List<T> m_list;
          private int m_idNext;
          private T m_current;

         public  object Current
          {
              get
              {
                  if (this.m_idNext <= 0)
                  {
                      throw new InvalidOperationException();
                  }
                  return this.m_current;
              }
          }
          T IEnumerator<T>.Current
          {
              get
              {
                  return this.m_current;
              }
          }
          ...

GC測(cè)試

應(yīng)用于AB_List< int >的foreach

    void testABListForeach()
    {
        for (int i = 0; i < 1000; i++)
        {
            foreach (var iNum in m_intABList)
            {
            }
        }
    }
AB_List< int >的foreach

沒(méi)有產(chǎn)生GC

應(yīng)用于AB_List< int >的GetEnumerator

    void testABListGetEmulator()
    {
        for (int i = 0; i < 1000; i++)
        {
            var iNum = m_intABList.GetEnumerator();
            while (iNum.MoveNext())
            {
               var t=  iNum.Current;
            }
        }
    }
AB_List< int >的GetEnumerator

也沒(méi)有產(chǎn)生GC

局限性

本list雖然沒(méi)有GC,但是其使用也有一定的局限性。

最大的局限性是foreach嵌套的問(wèn)題。當(dāng)我們?cè)诤芏嘀厍短字型瑫r(shí)foreach這個(gè)list時(shí),相當(dāng)于需要多個(gè)枚舉器同時(shí)存在。List被設(shè)計(jì)為存儲(chǔ)一定數(shù)目的枚舉器組,以便于多次調(diào)用。(這樣設(shè)計(jì)是為了避免值類型枚舉器被當(dāng)做引用返回時(shí)造成的裝箱問(wèn)題)

不過(guò),一般而言,你應(yīng)該不需要那么多層的foreach嵌套,默認(rèn)允許同時(shí)嵌套5層,如果你需要超過(guò)5層的嵌套,則在ABList的構(gòu)造函數(shù)中傳入更多的層次就可以。
另外一個(gè)小小的限制就是,當(dāng)你使用getEnumerator()時(shí),需要把它們放在using中,大概是這樣:

            using (var iNum = m_intABList.GetEnumerator())
            {
            }

這樣做的目的是在代碼塊結(jié)束時(shí),調(diào)用枚舉器的Dispose方法,這樣可以讓這個(gè)枚舉器變成可重用狀態(tài)。

總結(jié)

Unity系統(tǒng)的泛型List存在的問(wèn)題是:它在finally中回收枚舉器時(shí)執(zhí)行了Box操作。自定義List時(shí),正確實(shí)現(xiàn)泛型格式的IEnumerable、IEnumerator是關(guān)鍵,需要避開(kāi)枚舉單元被Current時(shí),值類型被強(qiáng)制轉(zhuǎn)換成對(duì)象類型的Box操作。

總體上來(lái)說(shuō),這應(yīng)該是一個(gè)高效的,無(wú)GC的List,看官可以嘗試一哈。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、數(shù)組數(shù)組是一組使用數(shù)字索引的對(duì)象,這些對(duì)象屬于同一種類型。雖然C#為創(chuàng)建數(shù)組提供了直接的語(yǔ)言支持,但通用類型系...
    CarlDonitz閱讀 801評(píng)論 0 1
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,351評(píng)論 25 708
  • Windows下錄制gif圖片的工具很多,Linux下錄制的工具比較少之前一直都是用Android Studio自...
    Andy周閱讀 1,837評(píng)論 0 1
  • 所有的姑娘都似乎有著想象,或者說(shuō),幻想的這一屬性.我也是姑娘,自然,也就有著數(shù)不清的幻想. 小時(shí)候,想著我要是能飛...
    米爾立夏閱讀 254評(píng)論 0 0
  • 即使時(shí)光倒流,再讓我選擇一次,我認(rèn)為我依舊會(huì)做出這樣的選擇,這樣的環(huán)境、這樣的性格,其實(shí)只能這樣選擇,所以如今過(guò)得...
    中正之勢(shì)閱讀 636評(píng)論 0 1

友情鏈接更多精彩內(nèi)容