歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> 為什麼存在內存對齊

為什麼存在內存對齊

日期:2017/3/1 9:13:12   编辑:Linux編程

說到內存對齊,很多人都知道是怎麼回事。但是內存對齊該娘不是本文的重點,本文的重點是內存對齊有什麼好處。

CPU訪問某個數據時,要求其存儲地址必須是相應數據類型的自然邊界。對於存儲地址不在其相應類型自然邊界的數據,不支持非對齊數據訪問的CPU,會導致CPU異常;即使是支持非對齊數據訪問的CPU,也會嚴重影響程序效率。

假設非對齊訪問出現在位於操作系統之上的進程,且CPU不支持非對齊數據訪問,那麼對於出現CPU異常的情況,可能操作系統會對其進行處理,(1)將所需要的數據裝載,並返回,或者說(2)直接讓進程死掉。情形(2)不需要多做解釋;對情況(1)來說,非對齊訪問每次都要進入異常處理程序,相比於一條指令直接拿到數據,效率極其低下。

假設非對齊訪問出現在直接位於硬件之上的進程,且CPU不支持非對齊數據訪問,那麼對於出現CPU異常的情況來說,基本上的直觀反應是進程退出並出現堆棧信息。

現在假設有一個8字節數據如下,|表示數據開始位置,|-|表示自然邊界

  |-|BBBBB|BBB|-|BBBBB|BBB

其前三字節為前一個對齊的八字節數據的後三字節,其後五字節為後一個對齊的八字節數據的前五字

  對於不支持非對齊裝載指令的CPU來說,要裝載這樣的一個數據,需要先裝載前一個八字節數據,再裝載後一個八字節數據,然後將前一個八字節數據的後三字節與後一個八字節數據的前五字節數據合並才能得到結果,與對齊數據的訪問相比,多了一個裝載指令以及相關合並指令的開銷,一般來說,在忽視緩存未命中的情況下,裝載指令的執行與得到結果之間是存在額外開銷的,因此這個差別是很大的,何況上邊說的,假設是在操作系統對CPU異常進行處理時為其加載數據,那麼異常處理程序的開銷可能更大;對非對齊數據的寫入時也需要額外的加載,合並操作。

  即使對於支持非對齊數據加載的CPU,依然會極大的影響效率,差別只是它省略掉了CPU異常處理過程。

  再進一層,假設之前描述的非對齊數據剛好橫跨兩個cache line,而且這兩個cache line至少有一個不在cache中(雖然對齊數據也會存在未命中,但是與非對齊相比,它不會橫跨兩個cache line),那麼這個訪問效率絕對不是多幾十條指令的問題了。

  因此,內存不對齊的壞處不是浪費內存,因為即使我寫一個隨便在不同位置放置不同大小的數據結構時,只要告訴編譯器說必須按照一字節對齊,編譯器編譯時肯定按照我的意願不浪費一個字節的內存。編譯器默認按照自然邊界對齊,是因為它要求效率,保證程序的正常運行(因為非對齊訪問可能導致進程退出)。我們對結構體的組織的調整是為了節約內存,而調整的規則就是按照內存對齊來安插數據。

每個特定平台上的編譯器都有自己的默認“對齊系數”(也叫對齊模數)。程序員可以通過預編譯命令#pragma pack(n),n=1,2,4,8,16來改變這一系數,其中的n就是你要指定的“對齊系數”。

規則:

1、數據成員對齊規則:結構(struct)(或聯合(union))的數據成員,第一個數據成員放在offset為0的地方,以後每個數據成員的對齊按照#pragma pack指定的數值和這個數據成員自身長度中,比較小的那個進行。

2、結構(或聯合)的整體對齊規則:在數據成員完成各自對齊之後,結構(或聯合)本身也要進行對齊,對齊將按照#pragma pack指定的數值和結構(或聯合)最大數據成員長度中,比較小的那個進行。

3、結合1、2可推斷:當#pragma pack的n值等於或超過所有數據成員長度的時候,這個n值的大小將不產生任何效果。

Win32平台下的微軟C編譯器的對齊策略:

1)結構體變量的首地址是其最長基本類型成員的整數倍;

備注:編譯器在給結構體開辟空間時,首先找到結構體中最寬的基本數據類型,然後尋找內存地址能是該基本數據類型的整倍的位置,作為結構體的首地址。將這個最寬的基本數據類型的大小作為上面介紹的對齊模數。

2)結構體每個成員相對於結構體首地址的偏移量(offset)都是成員大小的整數倍,如有需要編譯器會在成員之間加上填充字節(internal adding);

備注:為結構體的一個成員開辟空間之前,編譯器首先檢查預開辟空間的首地址相對於結構體首地址的偏移是否是本成員的整數倍,若是,則存放本成員,反之,則在本成員和上一個成員之間填充一定的字節,以達到整數倍的要求,也就是將預開辟空間的首地址後移幾個字節。

3)結構體的總大小為結構體最寬基本類型成員大小的整數倍,如有需要,編譯器會在最末一個成員之後加上填充字節。

備注:

a、結構體總大小是包括填充字節,最後一個成員滿足上面兩條以外,還必須滿足第三條,否則就必須在最後填充幾個字節以達到本條要求。

b、如果結構體內存在長度大於處理器位數的元素,那麼就以處理器的倍數為對齊單位;否則,如果結構體內的元素的長度都小於處理器的倍數的時候,便以結構體裡面最長的數據元素為對齊單位。

4) 結構體內類型相同的連續元素將在連續的空間內,和數組一樣。

總結:

1、平台原因(移植原因):不是所有的硬件平台都能訪問任意地址上的任意數據的;某些硬件平台只能在某些地址處取某些特定類型的數據,否則拋出硬件異常。

2、性能原因:數據結構(尤其是棧)應該盡可能地在自然邊界上對齊。原因在於,為了訪問未對齊的內存,處理器需要作兩次內存訪問;而對齊的內存訪問僅需要一次訪問。

Copyright © Linux教程網 All Rights Reserved