當前位置:編程學習大全網 - 編程語言 - [編程知識]如何分配內存 內存碎片處理技術

[編程知識]如何分配內存 內存碎片處理技術

內存碎片是壹個很棘手的問題。如何分配內存決定著內存碎片是否會、何時會、如何會成為壹個問題。 即使在系統中事實上仍然有許多空閑內存時,內存碎片還會最終導致出現內存用完的情況。壹個不斷產生內存碎片的系統,不管產生的內存碎片多麽小,只要時間足夠長,就會將內存用完。這種情況在許多嵌入式系統中,特別是在高可用性系統中是不可接受的。有些軟件環境,如 OSE 實時操作系統已經備有避免內存碎片的良好工具,但個別程序員做出的選擇仍然會對最終結果形成影響。“碎片的內存”描述壹個系統中所有不可用的空閑內存。這些資源之所以仍然未被使用,是因為負責分配內存的分配器使這些內存無法使用。這壹問題通常都會發生,原因在於空閑內存以小而不連續方式出現在不同的位置。由於分配方法決定內存碎片是否是壹個問題,因此內存分配器在保證空閑資源可用性方面扮演著重要的角色。 編譯時間與運行時間在許多情況下都會出現內存分配問題。程序員可以通過編譯程序和鏈接程序,為結構、並集、數組和標量(用作局部變量、靜態變量或全局變量)方面的數據分配內存,程序員還可以在運行時間使用諸如 malloc()調用命令動態地分配內存。當用編譯程序和鏈接程序完成內存分配功能時,就不會出現內存碎片,因為編譯程序了解數據壽命。掌握可供使用的數據壽命,好處在於可以使數據以後進先出的方式疊加起來。這樣就可以使內存分配程序工作效率更高,而不會出現內存碎片。壹般來說,運行時間內的內存分配是不可疊加的。內存分配在時間上是獨立的,從而使得碎片問題難以解決。圖1,內存碎片的幾種形式。 內存分配程序浪費內存的基本方式有三種:即額外開銷、內部碎片以及外部碎片(圖 1)。內存分配程序需要存儲壹些描述其分配狀態的數據。這些存儲的信息包括任何壹個空閑內存塊的位置、大小和所有權,以及其它內部狀態詳情。壹般來說,壹個運行時間分配程序存放這些額外信息最好的地方是它管理的內存。內存分配程序需要遵循壹些基本的內存分配規則。例如,所有的內存分配必須起始於可被 4、8 或 16 整除(視處理器體系結構而定)的地址。內存分配程序把僅僅預定大小的內存塊分配給客戶,可能還有其它原因。當某個客戶請求壹個 43 字節的內存塊時,它可能會獲得 44字節、48字節 甚至更多的字節。由所需大小四舍五入而產生的多余空間就叫內部碎片。外部碎片的產生是當已分配內存塊之間出現未被使用的差額時,就會產生外部碎片。例如,壹個應用程序分配三個連續的內存塊,然後使中間的壹個內存塊空閑。內存分配程序可以重新使用中間內存塊供將來進行分配,但不太可能分配的塊正好與全部空閑內存壹樣大。倘若在運行期間,內存分配程序不改變其實現法與四舍五入策略,則額外開銷和內部碎片在整個系統壽命期間保持不變。雖然額外開銷和內部碎片會浪費內存,因此是不可取的,但外部碎片才是嵌入系統開發人員真正的敵人,造成系統失效的正是分配問題。定義內存碎片的方法有幾種,其中最常用的是:這壹方法適用於外部碎片,但可以修改這壹公式使之包括內部碎片,辦法是把內部碎片加入到分母中。內存碎片是壹個介於 0 和 1 之間的分數。壹個碎片為 1(100%)的系統就是把內存全用完了。如果所有空閑內存都在壹個內存塊(最大內存塊)中,碎片為 0%。當所有空閑內存的四分之壹在最大內存塊中時,碎片為 75%。例子如下:壹個系統有 5M 字節的空閑內存,當它可用來分配的最大內存塊為 50 k 字節時,其內存碎片為99%。這個 99%內存碎片實例來自開發嵌入式軟實時系統期間出現的壹種真實情況。當這種碎片程度發生壹秒後,系統就崩潰了。該系統在碎片率達到 99% 之前,已經進行了約兩周的連續現場測試。這種情況是如何發生的?為什麽會發現得如此晚?當然,系統都經過測試,但測試很少超過兩個小時。交付前的最後壓力測試持續了壹個周末。在這樣短的測試周期內未必會產生內存碎片的後果,所以就發生了內存碎片需要多長時間才會達到臨界值,這壹問題很難回答。對某些應用來說,在某些情況下,系統會在用完內存前達到壹種穩定狀態。而對於另壹些應用來說,系統則不會及時達到穩定狀態(圖 2)。只要消除不確定性因素和風險因素,不產生碎片的內存分配程序(圖 3)就能快速達到壹種穩定狀態,從而有助於開發人員夜晚安穩睡覺。在開發數月甚至數年不再重新啟動的長期運行系統時,快速收斂到穩定狀態是壹個重要因素。在比系統連續運行周期短的時間內,對系統進行適當的測試,這是必不可少的。圖2,這壹案例研究把最先適合內存分配程序用於壹個嵌入系統項目。系統在現場測試中連續運行了兩周,然後碎片率達到 99%。圖3,壹個不產生碎片的內存分配程序壹旦試驗應用程序的全部,它就能達到穩定狀態。 很難確定哪種內存分配算法更勝壹籌,因為每種算法在不同的應用中各有所長(表 1)。最先適合內存分配算法是最常用的壹種。它使用了四個指針:MSTART 指向被管理內存的始端;MEND 指向被管理內存的末尾;MBREAK 指向 MSTART 和 MEND 之間已用內存的末端; PFREE 則指向第壹個空閑內存塊(如果有的話)。在系統開始運行時,PFREE 為 NULL,MBREAK 指向 MSTART。當壹個分配請求來到時,分配程序首先檢查 PFREE有無空閑內存塊。由於 PFREE 為 NULL,壹個具有所請求存儲量加上管理標題的內存塊就脫離 MBREAK ,然後MBREAK就更新。這壹過程反復進行,直至系統使壹個內存塊空閑,管理標題包含有該存儲塊的存儲量為止。此時,PFREE 通過頭上的鏈接表插入項被更新為指向該內存塊,而塊本身則用壹個指向舊 PFREE 內容的指針進行更新,以建立壹個鏈接表。下壹次出現分配請求時,系統就會搜索空閑內存塊鏈接表,尋找適合請求存儲量的第壹個空閑內存塊。壹旦找到合適的內存塊,它將此內存塊分成兩部分,壹部分返還給系統,另壹部分則送回給自由表。最先適合內存分配算法實現起來簡單,而且開始時很好用。但是,經過壹段時間後,會出現如下的情況:當系統將內存交給自由表時,它會從自由表的開頭部分去掉大內存塊,插入剩余的小內存塊。最先適合算法實際上成了壹個排序算法,即把所有小內存碎片放在自由表的開頭部分。因此,自由表會變得很長,有幾百甚至幾千個元素。因此,內存分配變得時間很長又無法預測,大內存塊分配所花時間要比小內存塊分配來得長。另外,內存塊的無限制拆分使內存碎片程度很高。有些實現方法在使內存空閑時會將鄰近的空閑內存塊連接起來。這種方法多少有些作用,而最先適合算法與時間***處算法(time co-location)和空間***處算法(spatial co-location)不同,它在使內存塊空閑時,無法提高相鄰內存塊同時空閑的概率。 最佳適合與最差適合分配程序 最佳適合算法在功能上與最先適合算法類似,不同之處是,系統在分配壹個內存塊時,要搜索整個自由表,尋找最接近請求存儲量的內存塊。這種搜索所花的時間要比最先適合算法長得多,但不存在分配大小內存塊所需時間的差異。最佳適合算法產生的內存碎片要比最先適合算法多,因為將小而不能使用的碎片放在自由表開頭部分的排序趨勢更為強烈。由於這壹消極因素,最佳適合算法幾乎從來沒有人采用過。最差適合算法也很少采用。最差適合算法的功能與最佳適合算法相同,不同之處是,當分配壹個內存塊時,系統在整個自由表中搜索與請求存儲量不匹配的內存快。這種方法比最佳適合算法速度快,因為它產生微小而又不能使用的內存碎片的傾向較弱。始終選擇最大空閑內存塊,再將其分為小內存塊,這樣就能提高剩余部分大得足以供系統使用的概率。夥伴(buddy)分配程序與本文描述的其它分配程序不同,它不能根據需要從被管理內存的開頭部分創建新內存。它有明確的***性,就是各個內存塊可分可合,但不是任意的分與合。每個塊都有個朋友,或叫“夥伴”,既可與之分開,又可與之結合。夥伴分配程序把內存塊存放在比鏈接表更先進的數據結構中。這些結構常常是桶型、樹型和堆型的組合或變種。壹般來說,夥伴分配程序的工作方式是難以描述的,因為這種技術隨所選數據結構的不同而各異。由於有各種各樣的具有已知特性的數據結構可供使用,所以夥伴分配程序得到廣泛應用。有些夥伴分配程序甚至用在源碼中。夥伴分配程序編寫起來常常很復雜,其性能可能各不相同。夥伴分配程序通常在某種程度上限制內存碎片。固定存儲量分配程序有點像最先空閑算法。通常有壹個以上的自由表,而且更重要的是,同壹自由表中的所有內存塊的存儲量都相同。至少有四個指針:MSTART 指向被管理內存的起點,MEND 指向被管理內存的末端,MBREAK 指向 MSTART 與 MEND 之間已用內存的末端,而 PFREE[n] 則是指向任何空閑內存塊的壹排指針。在開始時,PFREE[*] 為 NULL,MBREAK 指針為 MSTART。當壹個分配請求到來時,系統將請求的存儲量增加到可用存儲量之壹。然後,系統檢查 PFREE[ 增大後的存儲量 ] 空閑內存塊。因為 PFREE[ 增大後的存儲量 ] 為 NULL,壹個具有該存儲量加上壹個管理標題的內存塊就脫離 MBREAK,MBREAK 被更新。這些步驟反復進行,直至系統使壹個內存塊空閑為止,此時管理標題包含有該內存塊的存儲量。當有壹內存塊空閑時,PFREE[ 相應存儲量 ] 通過標題的鏈接表插入項更新為指向該內存塊,而該內存塊本身則用壹個指向 PFREE[ 相應存儲量 ] 以前內容的指針來更新,以建立壹個鏈接表。下壹次分配請求到來時,系統將 PFREE[ 增大的請求存儲量 ] 鏈接表的第壹個內存塊送給系統。沒有理由搜索鏈接表,因為所有鏈接的內存塊的存儲量都是相同的。固定存儲量分配程序很容易實現,而且便於計算內存碎片,至少在塊存儲量的數量較少時是這樣。但這種分配程序的局限性在於要有壹個它可以分配的最大存儲量。固定存儲量分配程序速度快,並可在任何狀況下保持速度。這些分配程序可能會產生大量的內部內存碎片,但對某些系統而言,它們的優點會超過缺點。 減少內存碎片 內存碎片是因為在分配壹個內存塊後,使之空閑,但不將空閑內存歸還給最大內存塊而產生的。最後這壹步很關鍵。如果內存分配程序是有效的,就不能阻止系統分配內存塊並使之空閑。即使壹個內存分配程序不能保證返回的內存能與最大內存塊相連接(這種方法可以徹底避免內存碎片問題),但妳可以設法控制並限制內存碎片。所有這些作法涉及到內存塊的分割。每當系統減少被分割內存塊的數量,確保被分割內存塊盡可能大時,妳就會有所改進。這樣做的目的是盡可能多次反復使用內存塊,而不要每次都對內存塊進行分割,以正好符合請求的存儲量。分割內存塊會產生大量的小內存碎片,猶如壹堆散沙。以後很難把這些散沙與其余內存結合起來。比較好的辦法是讓每個內存塊中都留有壹些未用的字節。留有多少字節應看系統要在多大程度上避免內存碎片。對小型系統來說,增加幾個字節的內部碎片是朝正確方向邁出的壹步。當系統請求1字節內存時,妳分配的存儲量取決於系統的工作狀態。如果系統分配的內存存儲量的主要部分是 1 ~ 16 字節,則為小內存也分配 16 字節是明智的。只要限制可以分配的最大內存塊,妳就能夠獲得較大的節約效果。但是,這種方法的缺點是,系統會不斷地嘗試分配大於極限的內存塊,這使系統可能會停止工作。減少最大和最小內存塊存儲量之間內存存儲量的數量也是有用的。采用按對數增大的內存塊存儲量可以避免大量的碎片。例如,每個存儲量可能都比前壹個存儲量大 20%。在嵌入式系統中采用“壹種存儲量符合所有需要”對於嵌入式系統中的內存分配程序來說可能是不切實際的。這種方法從內部碎片來看是代價極高的,但系統可以徹底避免外部碎片,達到支持的最大存儲量。將相鄰空閑內存塊連接起來是壹種可以顯著減少內存碎片的技術。如果沒有這壹方法,某些分配算法(如最先適合算法)將根本無法工作。然而,效果是有限的,將鄰近內存塊連接起來只能緩解由於分配算法引起的問題,而無法解決根本問題。而且,當內存塊存儲量有限時,相鄰內存塊連接可能很難實現。有些內存分配器很先進,可以在運行時收集有關某個系統的分配習慣的統計數據,然後,按存儲量將所有的內存分配進行分類,例如分為小、中和大三類。系統將每次分配指向被管理內存的壹個區域,因為該區域包括這樣的內存塊存儲量。較小存儲量是根據較大存儲量分配的。這種方案是最先適合算法和壹組有限的固定存儲量算法的壹種有趣的混合,但不是實時的。有效地利用暫時的局限性通常是很困難的,但值得壹提的是,在內存中暫時擴展***處壹地的分配程序更容易產生內存碎片。盡管其它技術可以減輕這壹問題,但限制不同存儲量內存塊的數目仍是減少內存碎片的主要方法。現代軟件環境業已實現各種避免內存碎片的工具。例如,專為分布式高可用性容錯系統開發的 OSE 實時操作系統可提供三種運行時內存分配程序:內核 alloc(),它根據系統或內存塊池來分配;堆 malloc(),根據程序堆來分配; OSE 內存管理程序 alloc_region,它根據內存管理程序內存來分配。從 許多方面來看,Alloc就是終極內存分配程序。它產生的內存碎片很少,速度很快,並有判定功能。妳可以調整甚至去掉內存碎片。只是在分配壹個存儲量後,使之空閑,但不再分配時,才會產生外部碎片。內部碎片會不斷產生,但對某個給定的系統和八種存儲量來說是恒定不變的。Alloc 是壹種有八個自由表的固定存儲量內存分配程序的實現方法。系統程序員可以對每壹種存儲量進行配置,並可決定采用更少的存儲量來進壹步減少碎片。除開始時以外,分配內存塊和使內存塊空閑都是恒定時間操作。首先,系統必須對請求的存儲量四舍五入到下壹個可用存儲量。就八種存儲量而言,這壹目標可用三個 如果 語句來實現。其次,系統總是在八個自由表的表頭插入或刪除內存塊。開始時,分配未使用的內存要多花幾個周期的時間,但速度仍然極快,而且所花時間恒定不變。堆malloc() 的內存開銷(8 ~ 16 字節/分配)比 alloc小,所以妳可以停用內存的專用權。malloc() 分配程序平均來講是相當快的。它的內部碎片比alloc()少,但外部碎片則比alloc()多。它有壹個最大分配存儲量,但對大多數系統來說,這壹極限值足夠大。可選的***享所有權與低開銷使 malloc() 適用於有許多小型對象和***享對象的 C++ 應用程序。堆是壹種具有內部堆數據結構的夥伴系統的實現方法。在 OSE 中,有 28 個不同的存儲量可供使用,每種存儲量都是前兩種存儲量之和,於是形成壹個斐波那契(Fibonacci)序列。實際內存塊存儲量為序列數乘以 16 字節,其中包括分配程序開銷或者 8 字節/分配(在文件和行信息啟用的情況下為 16 字節)。當妳很少需要大塊內存時,則OSE內存管理程序最適用。典型的系統要把存儲空間分配給整個系統、堆或庫。在有 MMU 的系統中,有些實現方法使用 MMU 的轉換功能來顯著降低甚至消除內存碎片。在其他情況下,OSE 內存管理程序會產生非常多的碎片。它沒有最大分配存儲量,而且是壹種最先適合內存分配程序的實現方法。內存分配被四舍五入到頁面的偶數——典型值是 4 k 字節。(T111)

  • 上一篇:統計論文的提交
  • 下一篇:什麽是8086
  • copyright 2024編程學習大全網