溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

為什么存在內(nèi)存對齊

發(fā)布時間:2020-07-20 09:46:17 來源:網(wǎng)絡(luò) 閱讀:1261 作者:小止1995 欄目:編程語言

  說到內(nèi)存對齊,很多人都知道是怎么回事。但是內(nèi)存對齊該娘不是本文的重點,本文的重點是內(nèi)存對齊有什么好處。
  CPU訪問某個數(shù)據(jù)時,要求其存儲地址必須是相應(yīng)數(shù)據(jù)類型的自然邊界。對于存儲地址不在其相應(yīng)類型自然邊界的數(shù)據(jù),不支持非對齊數(shù)據(jù)訪問的CPU,會導(dǎo)致CPU異常;即使是支持非對齊數(shù)據(jù)訪問的CPU,也會嚴重影響程序效率。
   假設(shè)非對齊訪問出現(xiàn)在位于操作系統(tǒng)之上的進程,且CPU不支持非對齊數(shù)據(jù)訪問,那么對于出現(xiàn)CPU異常的情況,可能操作系統(tǒng)會對其進行處理,(1)將所需要的數(shù)據(jù)裝載,并返回,或者說(2)直接讓進程死掉。情形(2)不需要多做解釋;對情況(1)來說,非對齊訪問每次都要進入異常處理程序,相比于一條指令直接拿到數(shù)據(jù),效率極其低下。
  假設(shè)非對齊訪問出現(xiàn)在直接位于硬件之上的進程,且CPU不支持非對齊數(shù)據(jù)訪問,那么對于出現(xiàn)CPU異常的情況來說,基本上的直觀反應(yīng)是進程退出并出現(xiàn)堆棧信息。 

 現(xiàn)在假設(shè)有一個8字節(jié)數(shù)據(jù)如下,|表示數(shù)據(jù)開始位置,|-|表示自然邊界

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

    其前三字節(jié)為前一個對齊的八字節(jié)數(shù)據(jù)的后三字節(jié),其后五字節(jié)為后一個對齊的八字節(jié)數(shù)據(jù)的前五字

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

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

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

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

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

規(guī)則:

1、數(shù)據(jù)成員對齊規(guī)則:結(jié)構(gòu)(struct)(或聯(lián)合(union))的數(shù)據(jù)成員,第一個數(shù)據(jù)成員放在offset為0的地方,以后每個數(shù)據(jù)成員的對齊按照#pragma pack指定的數(shù)值和這個數(shù)據(jù)成員自身長度中,比較小的那個進行。

2、結(jié)構(gòu)(或聯(lián)合)的整體對齊規(guī)則:在數(shù)據(jù)成員完成各自對齊之后,結(jié)構(gòu)(或聯(lián)合)本身也要進行對齊,對齊將按照#pragma pack指定的數(shù)值和結(jié)構(gòu)(或聯(lián)合)最大數(shù)據(jù)成員長度中,比較小的那個進行。

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

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

1)結(jié)構(gòu)體變量的首地址是其最長基本類型成員的整數(shù)倍;

備注:編譯器在給結(jié)構(gòu)體開辟空間時,首先找到結(jié)構(gòu)體中最寬的基本數(shù)據(jù)類型,然后尋找內(nèi)存地址能是該基本數(shù)據(jù)類型的整倍的位置,作為結(jié)構(gòu)體的首地址。將這個最寬的基本數(shù)據(jù)類型的大小作為上面介紹的對齊模數(shù)。

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

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

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

備注:

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

b、如果結(jié)構(gòu)體內(nèi)存在長度大于處理器位數(shù)的元素,那么就以處理器的倍數(shù)為對齊單位;否則,如果結(jié)構(gòu)體內(nèi)的元素的長度都小于處理器的倍數(shù)的時候,便以結(jié)構(gòu)體里面最長的數(shù)據(jù)元素為對齊單位。

4) 結(jié)構(gòu)體內(nèi)類型相同的連續(xù)元素將在連續(xù)的空間內(nèi),和數(shù)組一樣。

總結(jié):

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

2、性能原因:數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應(yīng)該盡可能地在自然邊界上對齊。原因在于,為了訪問未對齊的內(nèi)存,處理器需要作兩次內(nèi)存訪問;而對齊的內(nèi)存訪問僅需要一次訪問。


向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI