<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
前面有篇文章我們學習了 Go 語言空結構體詳解,最近又在看 unsafe包的知識,在查閱相關資料時不免會看到記憶體對齊相關的內容,雖然感覺這類知識比較底層,但是看到了卻不深究和渣男有什麼區別?雖然我不會,但我可以學,那麼這篇文章,我們就一起來看下什麼是記憶體對齊吧!
說明:本文中的測試範例,均是基於Go1.17 64位元機器
在Go語言中,我們可以通過 unsafe.Sizeof(x)
來確定一個變數佔用的記憶體位元組數(不包含 x 所指向的內容的大小)。
例如對於字串陣列,在64位元機器上,unsafe.Sizeof() 返回的任意字串陣列大小為 24 位元組,和其底層資料無關:
func main() { s := []string{"1", "2", "3"} s2 := []string{"1"} fmt.Println(unsafe.Sizeof(s)) // 24 fmt.Println(unsafe.Sizeof(s2)) // 24 }
對於Go語言的內建型別,佔用記憶體大小如下:
型別 | 位元組數 |
---|---|
bool | 1個位元組 |
intN, uintN, floatN, complexN | N/8 個位元組 (int32 是 4 個位元組) |
int, uint, uintptr | 計算機字長/8 (64位元 是 8 個位元組) |
*T, map, func, chan | 計算機字長/8 (64位元 是 8 個位元組) |
string (data、len) | 2 * 計算機字長/8 (64位元 是 16 個位元組) |
interface (tab、data 或 _type、data) | 2 * 計算機字長/8 (64位元 是 16 個位元組) |
[]T (array、len、cap) | 3 * 計算機字長/8 (64位元 是 24 個位元組) |
func main() { fmt.Println(unsafe.Sizeof(int(1))) // 8 fmt.Println(unsafe.Sizeof(uintptr(1))) // 8 fmt.Println(unsafe.Sizeof(map[string]string{})) // 8 fmt.Println(unsafe.Sizeof(string(""))) // 16 fmt.Println(unsafe.Sizeof([]string{})) // 24 var a interface{} fmt.Println(unsafe.Sizeof(a)) // 16 }
基於上面的理解,那麼對於一個結構體來說,佔用記憶體大小就應該等於多個基礎型別佔用記憶體大小的和,我們就結合幾個範例來看下:
type Example struct { a bool // 1個位元組 b int // 8個位元組 c string // 16個位元組 } func main() { fmt.Println(unsafe.Sizeof(Example{})) // 32 }
Example 結構體的三個基礎型別,加起來一個 25位元組
,但是最終輸出的卻是 32位元組
。
我們再看兩個結構體,即使這兩個結構體包含的欄位型別一致,但是順序不一致,最終輸出的大小也不一樣:
type A struct { a int32 b int64 c int32 } type B struct { a int32 b int32 c int64 } func main() { fmt.Println(unsafe.Sizeof(A{})) // 24 fmt.Println(unsafe.Sizeof(B{})) // 16 }
是什麼導致了上述問題的呢,這就引出了我們要看的知識點:記憶體對齊。
我們知道,在計算機中存取一個變數,需要存取它的記憶體地址,從理論上講似乎對任何型別的變數的存取可以從任何地址開始,但實際情況是:在存取特定型別變數的時候通常在特定的記憶體地址存取,這就需要對這些資料在記憶體中存放的位置有限制,各種型別資料按照一定的規則在空間上排列,而不是順序的一個接一個的排放,這就是對齊。
記憶體對齊是編譯器的管轄範圍。表現為:編譯器為程式中的每個“資料單元”安排在適當的位置上。
CPU
可以存取任意地址上的任意資料,而有些CPU
只能在特定地址存取資料,因此不同硬體平臺具有差異性,這樣的程式碼就不具有移植性,如果在編譯時,將分配的記憶體進行對齊,這就具有平臺可以移植性了。CPU
存取記憶體時並不是逐個位元組存取,而是以字長(word size)為單位存取,例如 32位元的CPU 字長是4位元組,64位元的是8位元組。如果變數的地址沒有對齊,可能需要多次存取才能完整讀取到變數內容,而對齊後可能就只需要一次記憶體存取,因此記憶體對齊可以減少CPU存取記憶體的次數,加大CPU存取記憶體的吞吐量。假設每次存取的步長為4個位元組,如果未經過記憶體對齊,獲取b的資料需要進行兩次記憶體存取,最後再進行資料整理得到b的完整資料:
image-20220313230839425
如果經過記憶體對齊,一次記憶體存取就能得到b的完整資料,減少了一次記憶體存取:
image-20220313231143302
unsafe.AlignOf(x) 方法的返回值是 m,當變數進行記憶體對齊時,需要保證分配到 x 的記憶體地址能夠整除 m
。因此可以通過這個方法,確定變數x 在記憶體對齊時的地址:
unsafe.Alignof(x)
至少為 1。unsafe.Alignof(x.f)
,unsafe.Alignof(x)
等於其中的最大值。unsafe.Alignof(x)
等於構成陣列的元素型別的對齊倍數。對於系統內建基礎型別變數 x ,unsafe.Alignof(x)
的返回值就是 min(字長/8,unsafe.Sizeof(x))
,即計算機字長與型別佔用記憶體的較小值:
func main() { fmt.Println(unsafe.Alignof(int(1))) // 1 -- min(8,1) fmt.Println(unsafe.Alignof(int32(1))) // 4 -- min (8,4) fmt.Println(unsafe.Alignof(int64(1))) // 8 -- min (8,8) fmt.Println(unsafe.Alignof(complex128(1))) // 8 -- min(8,16) }
我們講記憶體對齊,就是把變數放在特定的地址,那麼如何計算特定地址呢,這就涉及到記憶體對齊規則:
成員對齊規則
針對一個基礎型別變數,如果 unsafe.AlignOf()
返回的值是 m,那麼該變數的地址需要 被m整除
(如果當前地址不能整除,填充空白位元組,直至可以整除)。
整體對齊規則
針對一個結構體,如果 unsafe.AlignOf()
返回值是 m,需要保證該結構體整體記憶體佔用是 m的整數倍
,如果當前不是整數倍,需要在後面填充空白位元組。
通過記憶體對齊後,就可以保證在存取一個變數地址時:
例1:
type A struct { a int32 b int64 c int32 } func main() { fmt.Println(unsafe.Sizeof(A{1, 1, 1})) // 24 }
1.第一個欄位是 int32 型別,unsafe.Sizeof(int32(1))=4,記憶體佔用為4個位元組,同時unsafe.Alignof(int32(1)) = 4,記憶體對齊需保證變數首地址可以被4整除,我們假設地址從0開始,0可以被4整除:
成員變數1記憶體對齊
2.第二個欄位是 int64 型別,unsafe.Sizeof(int64(1)) = 8,記憶體佔用為 8 個位元組,同時unsafe.Alignof(int64(1)) = 8,需保證變數放置首地址可以被8整除,當前地址為4,距離4最近的且可以被8整除的地址為8,因此需要新增四個空白位元組,從8開始放置:
成員變數2記憶體對齊
3.第三個欄位是 int32 型別,unsafe.Sizeof(int32(1))=4,記憶體佔用為4個位元組,同時unsafe.Alignof(int32(1)) = 4,記憶體對齊需保證變數首地址可以被4整除,當前地址為16,16可以被4整除:
成員變數3記憶體對齊
4.所有成員對齊都已經完成,現在我們需要看一下整體對齊規則:unsafe.Alignof(A{}) = 8,即三個變數成員的最大值,記憶體對齊需要保證該結構體的記憶體佔用是 8 的整數倍,當前記憶體佔用是 20個位元組,因此需要再補充4個位元組:
整體對齊
5.最終該結構體的記憶體佔用為 24位元組。
例二:
type B struct { a int32 b int32 c int64 } func main() { fmt.Println(unsafe.Sizeof(B{1, 1, 1})) // 16 }
1.第一個欄位是 int32 型別,unsafe.Sizeof(int32(1))=4,記憶體佔用為4個位元組,同時unsafe.Alignof(int32(1)) = 4,記憶體對齊需保證變數首地址可以被4整除,我們假設地址從0開始,0可以被4整除:
成員變數1記憶體對齊
2.第二個欄位是 int32 型別,unsafe.Sizeof(int32(1))=4,記憶體佔用為4個位元組,同時unsafe.Alignof(int32(1)) = 4,記憶體對齊需保證變數首地址可以被4整除,當前地址為4,4可以被4整除:
成員變數2記憶體對齊
3.第三個欄位是 int64 型別,unsafe.Sizeof(int64(1))=8,記憶體佔用為8個位元組,同時unsafe.Alignof(int64(1)) = 8,記憶體對齊需保證變數首地址可以被8整除,當前地址為8,8可以被8整除:
成員變數3記憶體對齊
4.所有成員對齊都已經完成,現在我們需要看一下整體對齊規則:unsafe.Alignof(B{}) = 8,即三個變數成員的最大值,記憶體對齊需要保證該結構體的記憶體佔用是 8 的整數倍,當前記憶體佔用是 16個位元組,已經符合規則,最終該結構體的記憶體佔用為 16個位元組。
如果空結構體作為結構體的內建欄位:當變數位於結構體的前面和中間時,不會佔用記憶體;當該變數位於結構體的末尾位置時,需要進行記憶體對齊,記憶體佔用大小和前一個變數的大小保持一致。
type C struct { a struct{} b int64 c int64 } type D struct { a int64 b struct{} c int64 } type E struct { a int64 b int64 c struct{} } type F struct { a int32 b int32 c struct{} } func main() { fmt.Println(unsafe.Sizeof(C{})) // 16 fmt.Println(unsafe.Sizeof(D{})) // 16 fmt.Println(unsafe.Sizeof(E{})) // 24 fmt.Println(unsafe.Sizeof(F{})) // 12 }
本篇文章我們一起學習了Go 語言中的記憶體對齊,主要內容如下:
以上就是詳解Go語言中的記憶體對齊的詳細內容,更多關於Go語言記憶體對齊的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45