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

来源:http://www.cnblogs.com/yahle/archive/2017/06/24/7073929.html
-Advertisement-
Play Games

將長生命周期對象和大對象池化 請記住最開始說的原則:對象要麼立即回收要麼一直存在。它們要麼在0代被回收,要麼在2代里一直存在。有些對象本質是靜態的,生命周期從它們被創建開始,到程式停止才會結束。其它對象顯然不需要永遠存在下去,但他們的生命周期會存在程式的某些上下文里。它們的存活時間會超過0代(1代) ...


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

請記住最開始說的原則:對象要麼立即回收要麼一直存在。它們要麼在0代被回收,要麼在2代里一直存在。有些對象本質是靜態的,生命周期從它們被創建開始,到程式停止才會結束。其它對象顯然不需要永遠存在下去,但他們的生命周期會存在程式的某些上下文里。它們的存活時間會超過0代(1代)回收。這些類型的對象可以作為池化對象的備選。這雖然需要你手動管理記憶體,但實際情況下這是一個很好的選擇。另外一個重要的需要池化的對象是分配在LOH里的大對象。

沒有一個單一的標準方案或者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前收到消息通知,使用弱引用緩存對象


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 環境:Centos6.6 事先將需要的源碼包打包放在lamp.tar.gz中,並解壓到/root下 [root@zengqingfu ~]# lsanaconda-ks.cfg lamp.sh phpMyAdmin-4.2.5-all-languages.tar.gz 模板avg_score.awk ...
  • 第5節 環境變數與文件查找 《Linux 基礎入門(新版)》學習筆記 ...
  • 有限狀態機(finite state machine)簡稱FSM,表示有限個狀態及在這些狀態之間的轉移和動作等行為的數學模型,在電腦領域有著廣泛的應用。FSM是一種邏輯單元內部的一種高效編程方法,在伺服器編程中,伺服器可以根據不同狀態或者消息類型進行相應的處理邏輯,使得程式邏輯清晰易懂。 那有限狀 ...
  • Centos系統修改hostname的兩種方法——臨時修改與永久修改,另外還介紹了host解析文件的修改。 ...
  • Win10設置多時區時鐘方法技巧,本文小編將向大家介紹如何設置多個時區時鐘,以及如何使用「鬧鐘和時鐘」應用跟蹤更多時區,感興趣的朋友可以參考下麵教程操作。 Win10系統允許用戶配置最多3個不同的時鐘:除顯示本地時間的主時鐘外,還可以另外配置2個不同時區的時鐘。當你單擊或將滑鼠懸停在任務欄日期、時間 ...
  • 配置Ubuntu14.04防火牆 2017-06-23 ...
  • 當添加一個新賬號後,我們可能會發現新賬號sudo 時會報告不在sudoers中,使用su -s時輸入密碼後也會認證失敗 上網搜索大部分都要求修改/etc/sudoers中的內容,但修改這個文件必須需要許可權,這就陷入了死迴圈,故而我們需要更改策略來迂迴更改文件: 1、使用另一個可以使用root許可權的賬 ...
  • 進入在學習redis的時候,在文中看到了關於MessagePack的簡介,發現非常有意思,於是就花了點時間大致瞭解了下。 MessagePack介紹: MessagePack is an efficient binary serialization format.It lets you exchan ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...