[翻譯] 編寫高性能 .NET 代碼--第二章 GC -- 將長生命週期對象和大對象池化

將長生命週期對象和大對象池化

請記住最開始說的原則:對象要麼當即回收要麼一直存在。它們要麼在0代被回收,要麼在2代裏一直存在。有些對象本質是靜態的,生命週期從它們被建立開始,到程序中止纔會結束。其它對象顯然不須要永遠存在下去,但他們的生命週期會存在程序的某些上下文裏。它們的存活時間會超過0代(1代)回收。這些類型的對象能夠做爲池化對象的備選。這雖然須要你手動管理內存,但實際狀況下這是一個很好的選擇。另一個重要的須要池化的對象是分配在LOH裏的大對象。html

沒有一個單一的標準方案或者API來實現對象的池化。 這須要你根據你的程序和對象的類型來設計對應方案。算法

對於如何管理池化對象,你能夠將其當作非託管資源(內存)來進行管理。.NET對於這類資源有一個種管理模式:IDisposable。在本章前面咱們介紹瞭如何實現這種模式。一個比較合理的方式是實現IDisposable接口,在Dispose方法裏將對象丟回對象池。
實現一個好的對象池策略並不簡單,他取決於你程序要如何使用,以及那種類型的對象須要進行池化。
下面的栗子,實現了一個簡單的對象池,你能夠從裏面知道對象池會涉及那些內容。這個代碼能夠從 PooledObjects 的栗子工程裏看到。緩存

interface IPoolableObject : IDisposable
    {
        int Size { get; }
        void Reset();
        void SetPoolManager(PoolManager poolManager);
    }

    internal class PoolManager
    {
        private class Pool
        {
            public int PooledSize { get; set; }

            public int Count
            {
                get { return this.Stack.Count; }
            }

            public Stack<IPoolableObject> Stack { get; private set; }

            public Pool()
            {
                this.Stack = new Stack<IPoolableObject>();
            }
        }

        private const int MaxSizePerType = 10*(1 << 10); // 10 MB 

        private Dictionary<Type, Pool> pools = new Dictionary<Type, Pool>();

        public int TotalCount
        {
            get
            {
                int sum = 0;
                foreach (var pool in this.pools.Values)
                {
                    sum += pool.Count;
                }
                return sum;
            }
        }

        public T GetObject<T>() where T : class, IPoolableObject, new()
        {
            Pool pool;
            T valueToReturn = null;
            if (pools.TryGetValue(typeof (T), out pool))
            {
                if (pool.Stack.Count > 0)
                {
                    valueToReturn = pool.Stack.Pop() as T;
                }
            }
            if (valueToReturn == null)
            {
                valueToReturn = new T();
            }
            valueToReturn.SetPoolManager(this);
            return valueToReturn;
        }

        public void ReturnObject<T>(T value) where T : class, IPoolableObject, new()
        {
            Pool pool;
            if (!pools.TryGetValue(typeof (T), out pool))
            {
                pool = new Pool();
                pools[typeof (T)] = pool;
            }
            if (value.Size + pool.PooledSize < MaxSizePerType)
            {
                pool.PooledSize += value.Size;
                value.Reset();
                pool.Stack.Push(value);
            }
        }
    }

    internal class MyObject : IPoolableObject
    {
        private PoolManager poolManager;
        public byte[] Data { get; set; }
        public int UsableLength { get; set; }

        public int Size
        {
            get { return Data != null ? Data.Length : 0; }
        }

        void IPoolableObject.Reset()
        {
            UsableLength = 0;
        }

        void IPoolableObject.SetPoolManager(PoolManager poolManager)
        {
            this.poolManager = poolManager;
        }

        public void Dispose()
        {
            this.poolManager.ReturnObject(this);
        }
    }

強制讓每一個對象都實現接口會麻煩一些,但它除了方便外,還有一個重要的事實:爲了使對象池重用對象,你必須能徹底理解並控制它們。每次對象回到對象池前,你的代碼須要將對象從新設置到一個移植的,安全的狀態。這意味着你不該該天真的直接用第三方的對象池組件。你須要設計接口,並讓對象實現該接口,用來處理每一個對象獲取時的初始化過程。你還須要特別當心對.NET框架對象作池化。安全

特別須要注意的是用來作對象池的集合,由於它們的性質決定--你並不但願它們銷燬所存儲的數據(畢竟這是池的重點),但你須要一個能夠表示能夠爲空和可用空間的集合。幸運的是,大多數集合類型都實現了長度和容量的參數。考慮到使用現有的.NET集合類型會存在風險,建議最好本身實現集合類型,並實現一些標準的集合接口(如:IList ,ICollection 等)。相關建立本身定義集合的內容,可參考本書第六章。另一個策略就是讓你設計的可回收對象實現一個終結器(析構函數)。若是終結器運行,則意味着Dispose方法沒有執行,這將會是一個小小的bug。你也能夠在你的程序裏一些地方記錄日誌,崩潰信息或者一些信號信息。 網絡

請牢記,若是不清理對象池裏的數據,這等同於內存泄漏。你的對象池應該有一個邊界大小(不管是字節數量或者對象的數量),一旦超過,它應該通知GC清理多餘的對象。理想狀況下,你的對象池足夠大,能夠正常操做而不回收對象,但也會形成GC在執行回收時暫停時間變長,對象池裏對象越多回收算法耗時也越多。固然最重要的仍是對象池能知足你的須要。框架

我一般不會將對象池做爲默認的解決方案。它做爲一種通用機制,顯得很笨重以及容易出錯。但你可能會發現你的程序在某些類型上很適用對象池。在一個應用裏分配了大量的LOH對象,咱們調查後發現,能夠將一個單一的對象池化就能解決99%的問題。這個就是MemoryStream,咱們使用它來序列化網絡傳輸數據。實際的實現不單單是將構建了一個MemoryStream的隊列,由於要避免內存碎片,有一些更復雜的設計,但從本質上來講仍是將它池化。每次使用完MemoryStream對象,它都會被放入對象池裏。函數

下一篇:第二章 GC -- 減小大對象堆的碎片,在某些狀況下強制執行完整GC,按需壓縮大對象堆,在GC前收到消息通知,使用弱引用緩存對象this

相關文章
相關標籤/搜索