日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Go语言内存对齐详解

發布時間:2023/12/20 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Go语言内存对齐详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

你必須非常努力,才能看起來毫不費力!

微信搜索公眾號[ 漫漫Coding路 ],一起From Zero To Hero !

前言

前面有篇文章我們學習了 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)) // 24fmt.Println(unsafe.Sizeof(s2)) // 24 }

對于Go語言的內置類型,占用內存大小如下:

類型字節數
bool1個字節
intN, uintN, floatN, complexNN/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))) // 8fmt.Println(unsafe.Sizeof(uintptr(1))) // 8fmt.Println(unsafe.Sizeof(map[string]string{})) // 8fmt.Println(unsafe.Sizeof(string(""))) // 16fmt.Println(unsafe.Sizeof([]string{})) // 24var 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 int32b int64c int32 }type B struct {a int32b int32c int64 }func main() {fmt.Println(unsafe.Sizeof(A{})) // 24fmt.Println(unsafe.Sizeof(B{})) // 16 }

是什么導致了上述問題的呢,這就引出了我們要看的知識點:內存對齊

什么是內存對齊

我們知道,在計算機中訪問一個變量,需要訪問它的內存地址,從理論上講似乎對任何類型的變量的訪問可以從任何地址開始,但實際情況是:在訪問特定類型變量的時候通常在特定的內存地址訪問,這就需要對這些數據在內存中存放的位置有限制,各種類型數據按照一定的規則在空間上排列,而不是順序的一個接一個的排放,這就是對齊。

內存對齊是編譯器的管轄范圍。表現為:編譯器為程序中的每個“數據單元”安排在適當的位置上。

為什么需要內存對齊

  • 有些CPU可以訪問任意地址上的任意數據,而有些CPU只能在特定地址訪問數據,因此不同硬件平臺具有差異性,這樣的代碼就不具有移植性,如果在編譯時,將分配的內存進行對齊,這就具有平臺可以移植性了。

  • CPU 訪問內存時并不是逐個字節訪問,而是以字長(word size)為單位訪問,例如 32位的CPU 字長是4字節,64位的是8字節。如果變量的地址沒有對齊,可能需要多次訪問才能完整讀取到變量內容,而對齊后可能就只需要一次內存訪問,因此內存對齊可以減少CPU訪問內存的次數,加大CPU訪問內存的吞吐量。

  • 假設每次訪問的步長為4個字節,如果未經過內存對齊,獲取b的數據需要進行兩次內存訪問,最后再進行數據整理得到b的完整數據:

    如果經過內存對齊,一次內存訪問就能得到b的完整數據,減少了一次內存訪問:

    unsafe.AlignOf()

    unsafe.AlignOf(x) 方法的返回值是 m,當變量進行內存對齊時,需要保證分配到 x 的內存地址能夠整除 m。因此可以通過這個方法,確定變量x 在內存對齊時的地址:

    • 對于任意類型的變量 x ,unsafe.Alignof(x) 至少為 1。
    • 對于 struct 結構體類型的變量 x,計算 x 每一個字段 f 的 unsafe.Alignof(x.f),unsafe.Alignof(x) 等于其中的最大值。
    • 對于 array 數組類型的變量 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 int32b int64c int32 }func main() {fmt.Println(unsafe.Sizeof(A{1, 1, 1})) // 24 }
  • 第一個字段是 int32 類型,unsafe.Sizeof(int32(1))=4,內存占用為4個字節,同時unsafe.Alignof(int32(1)) = 4,內存對齊需保證變量首地址可以被4整除,我們假設地址從0開始,0可以被4整除:
  • 第二個字段是 int64 類型,unsafe.Sizeof(int64(1)) = 8,內存占用為 8 個字節,同時unsafe.Alignof(int64(1)) = 8,需保證變量放置首地址可以被8整除,當前地址為4,距離4最近的且可以被8整除的地址為8,因此需要添加四個空白字節,從8開始放置:
  • 第三個字段是 int32 類型,unsafe.Sizeof(int32(1))=4,內存占用為4個字節,同時unsafe.Alignof(int32(1)) = 4,內存對齊需保證變量首地址可以被4整除,當前地址為16,16可以被4整除:
  • 所有成員對齊都已經完成,現在我們需要看一下整體對齊規則:unsafe.Alignof(A{}) = 8,即三個變量成員的最大值,內存對齊需要保證該結構體的內存占用是 8 的整數倍,當前內存占用是 20個字節,因此需要再補充4個字節:
  • 最終該結構體的內存占用為 24字節。
  • 例二:

    type B struct {a int32b int32c int64 }func main() {fmt.Println(unsafe.Sizeof(B{1, 1, 1})) // 16 }
  • 第一個字段是 int32 類型,unsafe.Sizeof(int32(1))=4,內存占用為4個字節,同時unsafe.Alignof(int32(1)) = 4,內存對齊需保證變量首地址可以被4整除,我們假設地址從0開始,0可以被4整除:
  • 第二個字段是 int32 類型,unsafe.Sizeof(int32(1))=4,內存占用為4個字節,同時unsafe.Alignof(int32(1)) = 4,內存對齊需保證變量首地址可以被4整除,當前地址為4,4可以被4整除:
  • 第三個字段是 int64 類型,unsafe.Sizeof(int64(1))=8,內存占用為8個字節,同時unsafe.Alignof(int64(1)) = 8,內存對齊需保證變量首地址可以被8整除,當前地址為8,8可以被8整除:
  • 所有成員對齊都已經完成,現在我們需要看一下整體對齊規則:unsafe.Alignof(B{}) = 8,即三個變量成員的最大值,內存對齊需要保證該結構體的內存占用是 8 的整數倍,當前內存占用是 16個字節,已經符合規則,最終該結構體的內存占用為 16個字節。
  • 空結構體的對齊規則

    如果空結構體作為結構體的內置字段:當變量位于結構體的前面和中間時,不會占用內存;當該變量位于結構體的末尾位置時,需要進行內存對齊,內存占用大小和前一個變量的大小保持一致。

    type C struct {a struct{}b int64c int64 }type D struct {a int64b struct{}c int64 }type E struct {a int64b int64c struct{} }type F struct {a int32b int32c struct{} }func main() {fmt.Println(unsafe.Sizeof(C{})) // 16fmt.Println(unsafe.Sizeof(D{})) // 16fmt.Println(unsafe.Sizeof(E{})) // 24fmt.Println(unsafe.Sizeof(F{})) // 12 }

    總結

    本篇文章我們一起學習了Go 語言中的內存對齊,主要內容如下:

    • unsafe.Sizeof(x) 返回了變量x的內存占用大小
    • 兩個結構體,即使包含變量類型的數量相同,但是位置不同,占用的內存大小也不同,由此引出了內存對齊
    • 內存對齊包含成員對齊和整體對齊,與 unsafe.AlignOf(x) 息息相關
    • 空結構體作為成員變量時,是否占用內存和所處位置有關
    • 在實際開發中,我們可以通過調整變量位置,優化內存占用(一般按照變量內存大小順序排列,整體占用內存更小)

    更多

    個人博客: https://lifelmy.github.io/

    微信公眾號:漫漫Coding路

    總結

    以上是生活随笔為你收集整理的Go语言内存对齐详解的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。