天天看點

推薦一個跨平台記憶體配置設定器 - feixuwu - C++部落格一、使用方法二、如何替換CRT的malloc三、性能差别原因四、代碼細節五、其他

推薦一個跨平台記憶體配置設定器 - feixuwu - C++部落格

推薦一個跨平台記憶體配置設定器 - feixuwu - C++部落格

推薦一個跨平台記憶體配置設定器

  昨天一個同僚一大早在群裡推薦了一個google project上的開源記憶體配置設定器(http://code.google.com/p/google-perftools/),據說google的很多産品都用到了這個記憶體配置設定庫,而且經他測試,我們的遊戲用戶端內建了這個最新記憶體配置設定器後,FPS足足提高了将近10幀左右,這可是個了不起的提升,要知道3D組的兄弟忙了幾周也沒見這麼大的性能提升。

如果我們自己本身用的crt提供的記憶體配置設定器,這個提升也算不得什麼。問題是我們内部系統是有一個小記憶體管理器的,一般來說小記憶體配置設定的算法都大同小異,現成的實作也很多,比如linux核心的slab、SGI STL的配置設定器、ogre自帶的記憶體配置設定器,我們自己的記憶體配置設定器也和前面列舉的實作差不多。讓我們來看看這個項目有什麼特别的吧。

一、使用方法

打開首頁,由于公司網絡禁止SVN從外部更新,是以隻能下載下傳了打包的源代碼。解壓後,看到有個doc目錄,進去,打開使用文檔,發現使用方法極為簡單:

To use TCMalloc, just link TCMalloc into your application via the

"-ltcmalloc" linker flag.再看算法,也沒什麼特别的,還是和slab以及SGI STL配置設定器類似的算法。

unix環境居然隻要連結這個tcmalloc庫就可以了!,太友善了,不過我手頭沒有linux環境,文檔上也沒提到windows環境怎麼使用,

打開源代碼包,有個vs2003解決方案,打開,随便挑選一個測試項目,檢視項目屬性,發現僅僅有2點不同:

1、連結器指令行裡多了

  "..\..\release\libtcmalloc_minimal.lib",就是連結的時候依賴了這個記憶體優化庫。

2、連結器->輸入->強制符号引用 多了 __tcmalloc。

這樣就可以正确的使用tcmalloc庫了,測試了下,測試項目運作OK!

二、如何替換CRT的malloc

從前面的描述可知,項目強制引用了__tcmalloc, 搜尋了測試代碼,沒發現用到_tcmalloc相關的函數和變量,這個選項應該是為了防止dll被優化掉(因為代碼裡沒有什麼地方用到這個dll的符号)。

初看起來,連結這個庫後,不會影響任何現有代碼:我們沒有引用這個Lib庫的頭檔案,也沒有使用過這個dll的導出函數。那麼這個dll是怎麼優化應用程式性能的呢?

實際調試,果然發現問題了,看看如下代碼

    void* pData = malloc(100);

00401085 6A 64            push        64h 

00401087 FF 15 A4 20 40 00 call        dword ptr [__imp__malloc (4020A4h)]

跟蹤 call malloc這句,step進去,發現是

78134D09 E9 D2 37 ED 97   jmp         `anonymous namespace'::LibcInfoWithPatchFunctions<8>::Perftools_malloc (100084E0h)

果然,從這裡開始,就跳轉到libtcmalloc提供的Perftools_malloc了。

原來是通過API挂鈎來實作無縫替換系統自帶的malloc等crt函數的,而且還是通過大家公認的不推薦的改寫函數入口指令來實作的,一般隻有在遊戲外挂和金山詞霸之類的軟體才會用到這樣的挂鈎技術,

而且金山詞霸經常需要更新更新檔解決不同系統相容問題。

三、性能差别原因

如前面所述,tcmalloc确實用了很hacker的辦法來實作無縫的替換系統自帶的記憶體配置設定函數(本人在使用這類技術通常是用來幹壞事的。。。),但是這也不足以解釋為什麼它的效率比我們自己的好那麼多。

回到tcmalloc 的手冊,tcmalloc除了使用正常的小記憶體管理外,對多線程環境做了特殊處理,這和我原來見到的記憶體配置設定器大有不同,一般的記憶體配置設定器作者都會偷懶,把多線程問題扔給使用者,大多是加

個bool型的模闆參數來表示是否是多線程環境,還美其名曰:可定制,末了還得吹噓下模闆的優越性。

tcmalloc是怎麼做的呢? 答案是每線程一個ThreadCache,大部分作業系統都會支援thread local storage 就是傳說中的TLS,這樣就可以實作每線程一個配置設定器了,

這樣,不同線程配置設定都是在各自的threadCache裡配置設定的。我們的項目的配置設定器由于是多線程環境的,是以不管三七二十一,全都加鎖了,性能自然就低了。

僅僅是如此,還是不足以将tcmalloc和ptmalloc2分個高下,後者也是每個線程都有threadCache的。

關于這個問題,doc裡有一段說明,原文貼出來:

ptmalloc2 also reduces lock contention by using per-thread arenas but

there is a big problem with ptmalloc2's use of per-thread arenas. In

ptmalloc2 memory can never move from one arena to another. This can

lead to huge amounts of wasted space.

大意是這樣的:ptmalloc2 也是通過tls來降低線程鎖,但是ptmalloc2各個線程的記憶體是獨立的,也就是說,第一個線程申請的記憶體,釋放的時候還是必須放到第一個線程池中(不可移動),這樣可能導緻大量記憶體浪費。

四、代碼細節

1、無縫替換malloc等crt和系統配置設定函數。

   前面提到tcmalloc會無縫的替換掉原有dll中的malloc,這就意味着使用tcmalloc的項目必須是 MD(多線程dll)或者MDd(多線程dll調試)。tcmalloc的dll定義了一個

static TCMallocGuard module_enter_exit_hook;

的靜态變量,這個變量會在dll加載的時候先于DllMain運作,在這個類的構造函數,會運作PatchWindowsFunctions來挂鈎所有dll的 malloc、free、new等配置設定函數,這樣就達到了替換功能,除此之外,

為了保證系統相容性,挂鈎API的時候還實作了智能分析指令,否則寫入第一條Jmp指令的時候可能會破環後續指令的完整性。

2、LibcInfoWithPatchFunctions 和ThreadCache。

LibcInfoWithPatchFunctions模闆類包含tcmalloc實作的優化後的malloc等一系列函數。LibcInfoWithPatchFunctions的模闆參數在我看來沒什麼用處,tcmalloc預設可以挂鈎

最多10個帶有malloc導出函數的庫(我想肯定是夠用了)。ThreadCache在每個線程都會有一個TLS對象:

__thread ThreadCache* ThreadCache::threadlocal_heap_。

3、可能的問題

設想下這樣一個情景:假如有一個dll 在tcmalloc之前加載,并且在配置設定了記憶體(使用crt提供的malloc),那麼在加載tcmalloc後,tcmalloc會替換所有的free函數,然後,在某個時刻,

在前面的那個dll代碼中釋放該記憶體,這豈不是很危險。實際測試發現沒有任何問題,關鍵在這裡:

 span = Static::pageheap()->GetDescriptor(p);

    if (!span) {

      // span can be NULL because the pointer passed in is invalid

      // (not something returned by malloc or friends), or because the

      // pointer was allocated with some other allocator besides

      // tcmalloc.  The latter can happen if tcmalloc is linked in via

      // a dynamic library, but is not listed last on the link line.

      // In that case, libraries after it on the link line will

      // allocate with libc malloc, but free with tcmalloc's free.

      (*invalid_free_fn)(ptr);  // Decide how to handle the bad free request

      return;

    }

tcmalloc會通過span識别這個記憶體是否自己配置設定的,如果不是,tcmalloc會調用該dll原始對應函數(這個很重要)釋放。這樣就解決了這個棘手的問題。

五、其他

其實tcmalloc使用的每個技術點我從前都用過,但是我從來沒想過用API挂鈎來實作這樣一個有趣的記憶體優化庫(即使想過,也是一閃而過就否定了)。

從tcmalloc得到靈感,結合常用的外挂技術,可以很輕松的開發一個獨立工具:這個工具可以挂載到指定程序進行記憶體優化,在我看來,這可能可以作為一個外挂輔助工具來優化那些

記憶體優化做的很差導緻幀速很低的國産遊戲。

posted on 2012-07-24 14:38  lexus 閱讀( ...) 評論( ...) 編輯 收藏

轉載于:https://www.cnblogs.com/lexus/archive/2012/07/24/2606477.html

繼續閱讀