【Unity优化】构建一个拒绝GC的Lis

来源:互联网 发布:php aes解密后有乱码 编辑:程序博客网 时间:2024/05/18 16:39

版权声明:本文为博主原创文章,欢迎转载。请保留博主链接:http://blog.csdn.net/andrewfan
上篇文章《【Unity优化】Unity中究竟能不能使用foreach?》发表之后,曾经有网友说,在他的不同的Unity版本上,发现了泛型List无论使用foreach还是GetEnumerator均会产生GC的情况,这就有点尴尬了。由于它本身就是Mono编译器和相应.net库才能决定的原因,这就使得在使用系统提供的List时,又能最终摆脱GC的纠缠变得很困难。于是抓耳挠腮,翻出差不多六七年为Java代码写的动态数组,然后大肆修改一番。最终好像终于逃离GC的魔咒。

先奉上代码:

自定义的List

using System;using System.Collections;using System.Collections.Generic;using UnityEngine;namespace AndrewBox.Math{    /// <summary>    ///  动态数组    ///  @author AndrewFan    /// </summary>    /// <typeparam name="T">任意类型</typeparam>    public class AB_List<T> :IEnumerable<T>    {        protected int m_capacity;  // 容量        protected T[] m_items;// 内部数组        protected int m_length;// 存放的单元个数        public AB_List()        {            m_capacity = 10;            m_items = new T[m_capacity];        }        public AB_List(int capacity)        {            m_capacity = capacity;            m_items = new T[m_capacity];        }        /// <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            {                //取位于某个位置的单元                if (index < 0 || index >= m_length)                {                    throw new InvalidOperationException();                }                return m_items[index];            }            set            {                //设置位于某个位置的单元                if (index < 0 || index >= m_length)                {                    throw new InvalidOperationException();                }                m_items[index] = value;            }        }        /// <summary>        /// 增长容量        /// </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>        /// 清空单元数组        /// </summary>        public virtual void Clear()        {            for (int i = 0; i < m_length; i++)            {                m_items[i] = default(T);            }            m_length = 0;        }        /// <summary>        /// 是否包含某个单元        /// </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>        /// 获取指定单元在当前列表中的位置,从前向后查找        /// </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>        /// 获取指定单元在当前列表中的位置,从后先前查找        /// </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>        /// 获得长度        /// </summary>        public virtual int Count        {            get            {                return m_length;            }        }        /// <summary>        /// 移除指定位置的单元,如果单元归属权属于当前列表,则会将其卸载        /// </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>        /// 从指定位置开始(包括当前),移除后续单元,如果单元归属权属于当前列表,则会将其卸载        /// </summary>        /// <param name="index">要移除的位置</param>        /// <param name="innerMove">是否是内部移动</param>        /// <returns>被移除的个数,如果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>        /// 移除指定单元,如果单元归属权属于当前列表,则会将其卸载        /// </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>        /// 获取所有数据,注意这里的数据可能包含了很多冗余空数据,长度>=当前数组长度。        /// </summary>        /// <returns>所有数据数组</returns>        public T[] GetAllItems()        {            return m_items;        }        /// <summary>        /// 转换成定长数组,伴随着内容拷贝。        /// 如果是值类型数组,将与本动态数组失去关联;        /// 如果是引用类型数组,将与本动态数组保存相同的引用。        /// </summary>        /// <returns>数组</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>        /// 显示此数组,每个单元之间以逗号分隔        /// </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>        /// 显示此数组,每个单元一行        /// </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);        }        protected IEnumerator  m_enumerator;        protected IEnumerator<T>  m_enumeratorT;        public IEnumerator<T> GetEnumerator()        {            if (m_enumeratorT == null)            {                m_enumeratorT = new ABEnumerator<T>(this);            }            m_enumeratorT.Reset();            return m_enumeratorT;        }        IEnumerator  IEnumerable.GetEnumerator()        {            if (m_enumerator == null)            {                m_enumerator = new ABEnumerator<T>(this);            }            m_enumerator.Reset();            return m_enumerator;        }        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;                }            }            internal ABEnumerator(AB_List<T> l)            {                this.m_list = l;                this.m_idNext = 0;                m_current = default(T);            }            void IEnumerator.Reset()            {                this.m_idNext = 0;            }            public void Dispose()            {                //this.m_list = null;            }            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 ()     {        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++)        {            var iNum = m_intABList.GetEnumerator();            while (iNum.MoveNext())            {               var t=  iNum.Current;            }        }    }}

Foreach调用解析

关键之处作个解释:
首先理清楚IEnumerable、IEnumerator之间的关系。
IEnumerable是指那种可以被枚举的列表类型,如果我们自己自定义一个List,希望它能结合foreach使用的话,必须实现这个接口。
IEnumerator是一个枚举器。

系统库里的IEnumerable接口是这样:

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

在我的ABList类中实现接口的函数是下面这样:

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

目前的函数实现经过设计的话,它不会产生GC。然而,问题在后面紧紧跟随。实现了IEnumerable接口之后。当我们使用形如foreach(var t in list)的时刻,它就会去调用list中的继承于IEnumerator的Current实现:

namespace System.Collections{    public interface IEnumerator    {        object Current        {            get;        }        bool MoveNext();        void Reset();    }}

看到这里,它返回的是object,如果我们List中存放的是值类型,那么系统自然就产生了一次box装箱操作,GC于是悄悄地产生了。
也正是因为这个原因,微软后来加入了泛型的IEnumerator。但是,为了兼容以前的设计,这个泛型IEnumerator被设计成实现于之前的IEnumerator,而它的下方增加了同样的Current的Get方法。

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

同样的设计也被用于泛型的IEnumerable,

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

如果我们实现泛型的IEnumerable和IEnumerator,必须同时泛型和非泛型的GetEnumerator和Current方法。
那么,问题来了。现在有两个GetEnumerator()方法,两个Current的Get方法,究竟该用谁的呢?
首先,在实现的时候就需要加以区分:

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

这两个实现,你给其中一个加上public,另外一个就不能加上public;两个函数至少有一个需要增加[接口名称.]这种前缀;那么最终我们在foreach期间调用的就是public的那个方法。
自然,我们这里为了避免使用到非泛型IEnumerator中的Current方法的object返回形式,我们必须使用将唯一的生存权留给泛型的GetEnumerator。
同样,我们也需要在自定义的枚举器中作出选择。保留泛型的Current函数。

 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测试

应用于AB_List< int >的foreach

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

AB_List< int >的foreach

没有产生GC

应用于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

也没有产生GC

总结

Unity系统的泛型List存在的问题是:它在finally中回收枚举器时执行了Box操作。自定义List时,正确实现泛型格式的IEnumerable、IEnumerator是关键,需要避开枚举单元被Current时,值类型被强制转换成对象类型的Box操作。

0 0