2012-05-17 41 views
9

我正在使用C++編寫的大型服務器應用程序。此服務器需要運行幾個月而不重新啓動。因爲我們的內存消耗會隨着時間的推移而增加,所以碎片已經成爲一個可疑的問題到目前爲止,測量是將專用字節與虛擬字節進行比較,並分析這兩個數字之間的差異。在編碼時考慮內存碎片:過早優化與否?

我的碎片分析的一般方法是將其留給分析。我有同樣的方式來思考其他方面,比如一般性能和內存優化。您必須通過分析和證明來備份更改。

在代碼審查或討論期間,我注意到很多內存碎片是出現的第一件事情之一。這幾乎就像現在有一個巨大的恐懼,並且有一個很大的舉措來提前「防止碎片化」。要求更改代碼似乎有利於減少或防止內存碎片問題。我傾向於不同意這些蝙蝠,因爲它們看起來對我來說是過早的優化。我會犧牲代碼清潔/可讀性/可維護性等。以滿足這些變化。

例如,採取以下代碼:

std::stringstream s; 
s << "This" << "Is" << "a" << "string"; 

上面,字符串流使得這裏是不確定的分配的數量,也可能是4次分配,或僅有1分配。所以我們不能單獨進行優化,但一般的共識是要麼使用固定的緩衝區,要麼修改代碼以使用更少的分配。我真的沒有看到stringstream在這裏作爲一個巨大的內存問題貢獻者,但也許我錯了。上述

一般改進建議代碼是沿着線:

std::stringstream s; 
s << "This is a string"; // Combine it all to 1 line, supposedly less allocations? 

還有一個巨大的推動,其中以往任何時候都可以使用堆棧上堆。

是否有可能以這種方式搶佔內存碎片,或者這只是一種虛假的安全感?

+4

我想一個簡單的判斷方法是:你擔心程序中的兩件事:實現正確性和實現效率。我們都希望兩個類別中的最高水平,但實際上最好把重點放在正確性而不是效率上,因爲世界上最有效的錯誤程序仍然是錯誤的,仍然是無用的。你應該把精力集中在你的能力上; *過早優化只是意味着更多地關注效率而不是正確性*。如果您有能力在不犧牲正確性的情況下提高效率,您絕對應該這樣做! – GManNickG

+3

'在這裏,碎片已經是一個可疑的問題,因爲我們的內存消耗會隨着時間的推移而增加。「在我看來,簡單的舊內存泄漏將是一個更可能的懷疑 - 可能希望首先排除這些內存泄漏(並使用像valgrind或drmemory它也比檢查你的整個代碼庫的碎片來源更容易) – smocking

回答

14

這不是過早的優化,如果你事先知道你需要你已經預先測量低碎片該碎片是你你事先知道一個實際的問題,你的代碼段相關。性能是一項要求,但盲目優化在任何情況下都不好。

但是,優越的方法是使用無碎片的自定義分配器,如對象池或內存競技場,它保證沒有碎片。例如,在物理引擎中,您可以使用內存競技場進行所有每個節點的分配並在最後將其清空,這不僅非常快速(甚至比VS2010上的_alloca還快),而且還具有極高的內存效率和較低的碎片。

+0

作爲遊戲開發者,自定義分配器和池是非常普遍的,我們通常從一開始就構建它們。沒有理由不知道你會需要他們。在確定各種子系統的預算時,它也使事情變得更容易。 –

+0

我想這裏的普遍共識是因爲我們認爲碎片化已經是一個問題,未來的分配會放大這個問題。在這方面,他們認爲優化不是「盲目」進行的,但是我認爲每個案例都是具體的情況,存在分散問題可能與未來的分配無關。你怎麼看? –

+0

@RobertDailey:您仍然想要確定哪些分配實際上導致了分段。沒有理由相信隨機附加分配X會產生一些差異 - 就像添加一個隨機函數X並調用它一樣,即使您已經受到CPU限制,它也是無關緊要的。 – Puppy

1

我認爲這不僅僅是一種過早優化的最佳做法。例如,如果您有測試套件,則可以創建一組內存測試,以在夜間運行和測量內存,性能等。如果可能,您可以閱讀報告並修復一些錯誤。

小優化的問題是更改不同的代碼,但具有相同的業務邏輯。就像使用反向循環一樣,因爲它比常規的快。你的單元測試可能會指導你優化一些沒有副作用的點。

6

在算法級別考慮內存碎片是絕對合理的。在棧上分配小的固定大小的對象以避免不必要的堆分配和免費的成本也是合理的。但是,我肯定會在任何使代碼更難調試,分析或維護的問題上劃清界限。

我還擔心有很多建議都是錯誤的。人們通常所說的「避免記憶碎片」應該做的一半可能沒有任何效果,其餘的相當一部分可能是有害的。

對於典型現代計算機硬件上最實際,長期運行的服務器類型應用程序,用戶空間虛擬內存的碎片對於簡單直接轉發的編碼來說不會是個問題。

+0

你的第二個評論是非常重要的。造成分裂的原因並不總是很明顯,解決方案也不是。 –

-3

還有一點我想提的是:爲什麼不嘗試某種垃圾回收器。您可以在特定閾值之後或特定時間段之後調用它。垃圾收集器會在一定的閾值後自動收集未使用的內存。

還有關於碎片,請嘗試爲不同類型的對象分配某種類型的存儲並在您的代碼中管理它們。

即如果你有5種類型的對象(A,B,C,D和E類)。你可以在開始時分配空間,比如說開頭的每個類型的1000個對象,比如cacheA,cacheB ... cacheE。

所以,你會避免malloc和new的很多調用,以及碎片將會非常少。此外,代碼將像以前一樣可讀,因爲你只需要實現類似myAlloc的東西,它將從你的cacheA,cacheB等中分配...

1

在你真正遇到它之前要非常關心內存碎片顯然是不成熟的優化;在最初的設計中,我不會過多考慮它。像封裝好的東西更重要(因爲它們可以讓你稍後改變內存表示,如果你需要的話)。

另一方面,它良好的設計,以避免不必要的分配,並在可能時使用本地變量,而不是動態分配。不僅僅是出於碎片原因,而且出於程序簡單性的原因。一般而言,C++更傾向於使用值語義,而使用值語義(複製和賦值)的程序比使用引用語義(動態分配和傳遞指針)更自然。

0

我認爲你不應該在實際遇到它之前解決碎片問題,但同時你的軟件應該被設計成允許這樣一個解決方案易於集成的內存碎片問題。由於解決方案是自定義內存分配器,它意味着將一個代碼插入到您的代碼中(適用於您的容器的operator new/delete和Allocator類)應該通過在config.h文件中的某處更改一行代碼來完成,並且絕對不能通過經歷所有容器等的所有實例。支持這一點的另一點是,目前所有複雜軟件中有99%是多線程的,並且來自不同線程的內存分配會導致同步問題,有時會導致虛假分享。而這些問題的答案又是自定義內存分配器。因此,如果你的設計支持自定義分配器,那麼你不應該接受作爲「碎片釋放」而出售給你的代碼更改,除非你分析你的應用並親自看到該補丁確實減少了數量DTLB或LLC錯過了更好的包裝數據。但是,如果設計不允許使用自定義分配器,那麼在執行任何其他「消除內存碎片」代碼更改之前,應該將其作爲第一步實施。

從我對內部設計的記憶中,可以嘗試使用線程構建模塊可伸縮分配器來增加內存分配的可伸縮性並減少內存碎片。

另一個小點:你與stringstream的分配(S)和收拾分配儘可能多地在一起決策的例子 - 我的理解是,在某些情況下,這將導致內存碎片,而不是解決這個問題。將所有分配打包在一起會讓你請求連續的大塊內存,這可能最終分散,然後其他類似的大塊請求將無法彌補差距。