前文
話說今天在用uintptr進行指針運算的時候,突然想起來有個內存對齊的東西,那麼對這個uintptr計算是否會有影響?
帶着疑問,開始吧。
你將獲得以下知識點:
1.什麼是內存對齊?
2.爲什麼需要內存對齊?
3.如何進行內存對齊?
4.golang的內存對齊如何體現?
5.如何利用內存對齊來優化golang?
正文
1.什麼是內存對齊?
在想象中內存應該是一個一個獨立的字節組成的。像這樣:
事實上,人家是這樣的:
內存是按照成員的聲明順序,依次分配內存,第一個成員偏移量是0,其餘每個成員的偏移量爲指定數的整數倍數(圖中是4)。像這樣進行內存的分配叫做內存對齊。
2.爲什麼需要內存對齊?
原因有兩點:
平臺原因
並不是所有的硬件平臺都能訪問任意地址上的任意數據,會直接報錯的!
(解釋:比如說有的cpu讀取4個字節數據,要是沒有內存對齊,從1開始那麼內存就需要把0-7字節的全部取出來,再剔除掉1/5/6/7,增加了額外的操作,cpu不一定能這麼搞,自然就報錯了)
性能原因
訪問未對齊的內存,需要訪問兩次;如果對齊的話就只需要一次了。
(解釋:比如取int64,按照8個位對齊好了,那獲取的話直接就是獲取8個字節就好了,邊界好判斷)
3.如何進行內存對齊?
二個原則:
1.具體類型,對齊值=min(編譯器默認對齊值,類型大小Sizeof長度)
2.struct每個字段內部對齊,對齊值=min(默認對齊值,字段最大類型長度)
4.golang的內存對齊如何體現?
4.1 結構體的相同成員不同順序
結構體是平時寫代碼經常用到的。相同的成員,不同的排列順序,會有什麼區別嗎?
舉個例子:
func main() {
fmt.Println(unsafe.Sizeof(struct {
i8 int8
i16 int16
i32 int32
}{}))
fmt.Println(unsafe.Sizeof(struct {
i8 int8
i32 int32
i16 int16
}{}))
}
輸出:
8
12
what?竟然不一樣。
分析一波:需要內存對齊的話,因爲最大是int32,所以最終記過必須是4個字節的倍數才能對齊。
當8-16-32的時候,類似這樣|x-xx|xxxx|。
當8-32-16的時候,類似這樣|x—|xxxx|xx–|。
一眼就看出了大小了。
這裏的爲什麼是x-xx而不是xxx-需要說明下。因爲當int8放入內存的時候,其佔坑1個字節,對齊值爲1,而int16佔坑2個字節,對齊值爲2,所以說會先偏移2個字節從第三個字節纔開始放int16的數
4.2指針運算
現在對結構體Test通過指針計算的方式進行賦值。
Test內存情況:|x-xx|xxxx|。需要注意的是這裏的“-”需要多計算一個字節才行。
type Test struct {
i8 int8
i16 int16
i32 int32
}
func main() {
var t = new(Test)
// 從0開始
var i8 = (*int8)(unsafe.Pointer(t))
*i8 = int8(10)
// 偏移int8+1的字節數,注意這裏有個1!!!
var i16 = (*int16)(unsafe.Pointer(uintptr(unsafe.Pointer(t))+ uintptr(1) + uintptr(unsafe.Sizeof(int8(0)))))
*i16 = int16(10)
// 偏移int8+1+int16+的字節數,注意這裏有個1!!!
var i32 = (*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(t)) + uintptr(1) + uintptr(unsafe.Sizeof(int8(0))+uintptr(unsafe.Sizeof(int16(0))))))
*i32 = int32(10)
fmt.Println(*t)
}
輸出:
{10 10 10}
附上兩個神器:
功能 | 函數 |
---|---|
獲取對齊值 | unsafe.Alignof(t.i16) |
獲取偏移值 | unsafe.Offsetof(t.i16) |
5.如何利用內存對齊來優化golang?
5.1 結構體佔用內存過大的問題
根據計算對齊值進行成員順序的拼湊,可以一定程度上縮小結構體佔用的內存。
5.2 指針運算的坑
通過分析偏移量和對齊值,準確計算每個成員所偏移的位數,避免算錯。