內(nèi)存對齊原理
內(nèi)存對齊原理
對于程序而言,一個變量的數(shù)據(jù)存儲范圍是在一個尋址步長范圍內(nèi)的話,這樣一次尋址就可以讀取到變量的值,如果是超出了步長范圍內(nèi)的數(shù)據(jù)存儲,就需要讀取兩次尋址再進(jìn)行數(shù)據(jù)的拼接,效率明顯降低了。例如一個double類型的數(shù)據(jù)在內(nèi)存中占據(jù)8個字節(jié),如果地址是8,那么好辦,一次尋址就可以了,如果是20呢,那就需要進(jìn)行兩次尋址了。
這樣就產(chǎn)生了數(shù)據(jù)對齊的規(guī)則,也就是將數(shù)據(jù)盡量的存儲在一個步長內(nèi),避免跨步長的存儲,這就是內(nèi)存對齊。
在32位編譯環(huán)境下默認(rèn)4字節(jié)對齊,在64位編譯環(huán)境下默認(rèn)8字節(jié)對齊。 現(xiàn)代處理器一般都有多個級別的高速緩存,處理器訪問這些高速緩存里的數(shù)據(jù)的效率要比訪問內(nèi)存里的數(shù)據(jù)效率高得多(就像處理器訪問內(nèi)存里的數(shù)據(jù),比訪問磁盤里的數(shù)據(jù)效率高得多一樣。 就像上面介紹以的一樣,一般來說,CPU 總是以字大小(32 位處理器上常常為 4 個字節(jié))訪問數(shù)據(jù),所以如果數(shù)據(jù)沒有內(nèi)存對齊,CPU 訪問這些數(shù)據(jù)時,可能就需要執(zhí)行更多次的讀取操作才行。
在這樣的機(jī)器上,讀取 2 個字節(jié)數(shù)據(jù)往往比讀取 4 個字節(jié)數(shù)據(jù)慢得多。 訪問范圍提高 對于任意給定的地址空間,如果體系架構(gòu)可以確定 2 個 LSB 總是 0(例如 32 位機(jī)器),那么它可以訪問 4 倍多的內(nèi)存(2 個位能夠表示 4 個不同狀態(tài))。從一個地址中去掉 2 個 LSB,將得到 4 字節(jié)的內(nèi)存對齊,或者說“跨距”,因為地址每增加一,它就有效的增加 bit 2,而不是 bit 0。
(鑒于低 2 位總是 00) 這甚至?xí)绊懴到y(tǒng)的物理設(shè)計:如果地址總線的需要少 2 位,CPU 上的管腳就可以少 2 個。 前面提到 CPU 每次訪問數(shù)據(jù)的寬度是一個字,如果C語言程序中的數(shù)據(jù)總是內(nèi)存對齊的,那么 CPU 訪問數(shù)據(jù)總是原子性的,這對于許多無鎖數(shù)據(jù)結(jié)構(gòu)和其他并發(fā)需求的正確操作至關(guān)重要。 規(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ù)成員自身長度中,比較小的那個進(jìn)行。
2、 結(jié)構(gòu)(或聯(lián)合)的 整體對齊規(guī)則: 在數(shù)據(jù)成員完成各自對齊之后,結(jié)構(gòu)(或聯(lián)合)本身也要進(jìn)行對齊,對齊將按照#pragma pack指定的數(shù)值和結(jié)構(gòu)(或聯(lián)合)**數(shù)據(jù)成員長度中,比較小的那個進(jìn)行。 3、 結(jié)合1、2可推斷:當(dāng)#pragma pack的n值等于或超過所有數(shù)據(jù)成員長度的時候,這個n值的大小將不產(chǎn)生任何效果。 #pragma pack 其實(shí)就是指定內(nèi)存對齊系數(shù),如1,2,4,8,16。
內(nèi)存對齊的作用
CPU讀取內(nèi)存粒度一般是2,4,8,16字節(jié),當(dāng)CPU讀取非對齊內(nèi)存時,有可能需要兩次訪問,而對齊內(nèi)存只需要一次。 假設(shè)下面的結(jié)構(gòu)體是按1字節(jié)對齊,CPU讀取內(nèi)存粒度為4,那么當(dāng)訪問數(shù)據(jù)成員c時,CPU需要先讀取前4個字節(jié)的內(nèi)容,然后再讀取后4字節(jié)的內(nèi)容。
不是所有的硬件平臺都能訪問任意地址上的任意數(shù)據(jù)的,某些硬件平臺只能在某些地址處取某些特定類型的數(shù)據(jù),否則拋出硬件異常。
Intel/AMD的x86/x86-64架構(gòu)則始終支持支持非對齊內(nèi)存訪問,而在ARM架構(gòu)中,只有ARMv6及以上才支持非對齊內(nèi)存訪問。 每個數(shù)據(jù)成員的偏移量必須是數(shù)據(jù)成員自身長度和指定的對齊長度中較小的那個的倍數(shù)。 結(jié)構(gòu)體大小必須是結(jié)構(gòu)體中**數(shù)據(jù)成員長度和指定的對齊長度中較小的那個的倍數(shù)。
內(nèi)存對齊問題
1.平臺原因(移植原因): 不是所有的硬件平臺都能訪問任意地址上的任意數(shù)據(jù)的;某些硬件平臺只能 在某些地址處取某些特定類型的數(shù)據(jù),否則拋出硬件異常。 2.性能原因: 數(shù)據(jù)結(jié)構(gòu)應(yīng)該盡可能地在自然邊界上對齊。
原因在于,為了訪問未對齊的內(nèi)存,處理器需要作兩次內(nèi)存訪問;而對齊的內(nèi)存訪問僅需要一次訪問。
(如果是對齊的,那么CPU不需要跨越兩個操作字,不是對齊的則需要訪問兩個操作字才能拼接出需要的內(nèi)存地址) 指針的大小一般是一個機(jī)器字的大小 通過Go語言的structlayout工具,可以得出下圖 這些類型在之前的 slice 、 map 、 interface 已經(jīng)介紹過了,也特意強(qiáng)調(diào)過,makehmap函數(shù)返回的是一個指針,因此map的對齊為一個機(jī)器字. 回頭看看 sync.pool的防止copy的空結(jié)構(gòu)體字段,也是放在**位,破案了。 計算機(jī)結(jié)構(gòu)可能會要求內(nèi)存地址 進(jìn)行對齊;也就是說,一個變量的地址是一個因子的倍數(shù),也就是該變量的類型是對齊值。 函數(shù)Alignof接受一個表示任何類型變量的表達(dá)式作為參數(shù),并以字節(jié)為單位返回變量(類型)的對齊值。對于變量x: 這是因為int64在bool之后未對齊。
它是32位對齊的,但不是64位對齊的,因為我們使用的是32位系統(tǒng),因此實(shí)際上只是兩個32位值并排在一起。 ● 內(nèi)存對齊是為了cpu更高效訪問內(nèi)存中數(shù)據(jù) ● 結(jié)構(gòu)體對齊依賴類型的大小保證和對齊保證 ● 地址對齊保證是:如果類型 t 的對齊保證是 n,那么類型 t 的每個值的地址在運(yùn)行時必須是 n 的倍數(shù)。
結(jié)構(gòu)體內(nèi)存對齊
1.什么是內(nèi)存對齊? 2.為什么要做內(nèi)存對齊? 3.結(jié)構(gòu)體內(nèi)存對齊規(guī)則 4.源碼內(nèi)存對齊算法 計算機(jī)內(nèi)存都是以字節(jié)為單位劃分的,從理論上講似乎對任何類型的變量的訪問可以從任何地址開始,但是實(shí)際的計算機(jī)系統(tǒng)對基本類型數(shù)據(jù)在內(nèi)存中存放的位置有限制,它們會要求這些數(shù)據(jù)的首地址的值是某個數(shù)k(通常它為4或8的倍數(shù)),這就是所謂的內(nèi)存對齊。內(nèi)存對齊是一種在計算機(jī)內(nèi)存中排列數(shù)據(jù)(表現(xiàn)為變量的地址) 、訪問數(shù)據(jù)(表現(xiàn)為CPU讀取數(shù)據(jù))的一種方式。
內(nèi)存對齊包含了兩種相互獨(dú)立又相互關(guān)聯(lián)的部分:基本數(shù)據(jù)對齊和結(jié)構(gòu)體數(shù)據(jù)對齊 。
1.平臺原因(移植原因):不是所有的硬件平臺都能訪問任意地址上的任意數(shù)據(jù)的;某些硬件平臺只能在某些地址處取某些特定類型的數(shù)據(jù),否則拋出硬件異常。 2.性能原因:數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應(yīng)該盡可能地在自然邊界上對齊。原因在于,為了訪問未對齊的內(nèi)存,處理器需要作兩次內(nèi)存訪問;而對齊的內(nèi)存訪問僅需要一次訪問。 當(dāng)我們定義一個 struct 的時候,它在內(nèi)存中是怎么存儲的?占用了多少字節(jié)的內(nèi)存空間呢? 從打印結(jié)果看出一個問題,結(jié)構(gòu)體中變量相同,只是順序不同,結(jié)果影響結(jié)構(gòu)體占用內(nèi)存大小,這就是iOS中內(nèi)存對齊 1.數(shù)據(jù)成員對?規(guī)則:結(jié)構(gòu)(struct)(或聯(lián)合(union))的數(shù)據(jù)成員,第 一個數(shù)據(jù)成員放在offset為0的地方,以后每個數(shù)據(jù)成員存儲的起始位置要 從該成員大小或者成員的子成員大小(只要該成員有子成員,比如說是數(shù)組, 結(jié)構(gòu)體等)的整數(shù)倍開始(比如int為4字節(jié),則要從4的整數(shù)倍地址開始存 儲。
min(當(dāng)前開始的位置m,n) m=9 n=4 9 10 11 12 2.結(jié)構(gòu)體作為成員:如果一個結(jié)構(gòu)里有某些結(jié)構(gòu)體成員,則結(jié)構(gòu)體成員要從 其內(nèi)部**元素大小的整數(shù)倍地址開始存儲.(struct a里存有struct b,b 里有char,int ,double等元素,那b應(yīng)該從8的整數(shù)倍開始存儲.) 3.收尾工作:結(jié)構(gòu)體的總大小,也就是sizeof的結(jié)果,.必須是其內(nèi)部** 成員的整數(shù)倍.不足的要補(bǔ)?。 拿上面的兩個結(jié)構(gòu)體舉例說明 sizeof 最終得到的結(jié)果是該數(shù)據(jù)類型占用空間的大小 class百科_getInstanceSize 獲取類的實(shí)例對象所占用的內(nèi)存大小 malloc_size 獲取系統(tǒng)實(shí)際分配的內(nèi)存大小 算法原理:k + 15 >> 4 << 4 ,其中 右移4 + 左移4相當(dāng)于將后4位抹零,跟 k/16 * 16一樣 ,是16字節(jié)對齊算法,小于16就成0了 對于一個對象來說,其真正的對齊方式 是 8字節(jié)對齊,8字節(jié)對齊已經(jīng)足夠滿足對象的需求了 apple系統(tǒng)為了防止一切的容錯,采用的是16字節(jié)對齊的內(nèi)存,主要是因為采用8字節(jié)對齊時,兩個對象的內(nèi)存會緊挨著,顯得比較緊湊,而16字節(jié)比較寬松,利于蘋果以后的擴(kuò)展。