計算機最小單位,和結構體占用字節大小分析(內存對齊)


文檔查詢 :https://golang.google.cn/pkg/strconv/  

GO的編碼為UTF-8編碼

計算機的基本的存儲單元有:

   位(bit):二進制數中的一個數位,可以是0或者1,是計算機中數據的最小單位。二進制的一個“0”或一個“1”叫一位。

   字節(Byte,B):計算機中數據的基本單位,每8位組成一個字節。各種信息在計算機中存儲、處理至少需要一個字節

 

ASCIIS碼: 1個英文字母(不分大小寫)= 1個字節的空間

       1個中文漢字 = 2個字節的空間

                    1個ASCII碼 = 一個字節

UTF-8編碼:1個英文字符 = 1個字節

                   英文標點  = 1個字節

                   1個中文(含繁體) = 3個字節

        中文標點 = 3個字節

Unicode編碼:1個英文字符 = 2個字節

                    英文標點  = 2個字節

                    1個中文(含繁體) = 2個字節

                    中文標點 = 2個字節              

字(Word):兩個字節稱為一個字。漢字的存儲單位都是一個字。

擴展的存儲單位有:

   計算機存儲容量大小以字節數來度量,1024進位制:

      1024B=1K(千)B  

 

           1024KB=1M(兆)B 

 

           1024MB=1G(吉)B 

 

           1024GB=1T(太)B 

   這是常用的五個,至於PB,EB,ZB,YB,BB,NB,DB幾乎在日常使用中是不會遇到的。

Int8,Int16,Int32,Int64,后面的數字就代表這個數據類型占據的空間。

       Int8, 等於Byte, 占1個字節.

    Int16, 等於short, 占2個字節. -32768 32767

    Int32, 等於int, 占4個字節. -2147483648 2147483647

    Int64, 等於long, 占8個字節. -9223372036854775808 9223372036854775807

 

go 中  string 占16個字節

  另外, 還有一個Byte, 它等於byte, 0 - 255.

 

package main

import (
"fmt"
"unsafe"
)

type One struct {
id1 int8 //size為1
id2 int32 //size為4
id3 int8 //size為1
}
//分析 [1 0 0 0][1 1 1 1][1]
//必須為4的倍數 [1 0 0 0][1 1 1 1][1][0 0 0]

type Two struct {
id1 int32 //size為4
id2 int8 //size為1
id3 int8 //size為1
}
//分析[1 1 1 1][1][1]
//必須為4的倍數  [1 1 1 1][1][1][0 0]

func main() {
fmt.Println(unsafe.Sizeof(One{})) //size為12
fmt.Println(unsafe.Sizeof(Two{})) //size為8
fmt.Println(unsafe.Sizeof("222222222")) //size為16
fmt.Println(unsafe.Sizeof(111111111)) //size為8
fmt.Println(unsafe.Sizeof(1.1)) //size為8
}

 這樣我們在寫代碼定義結構體變量時,注意設定他的大小和順序

對齊內容可看一篇文章:https://zhuanlan.zhihu.com/p/53413177

  內次對齊: 數據類型的對齊邊界和平台對齊邊界,取較小的那個 (不同平台會有所不同)

 

 結構體字節大小:先要確定結構體每個成員的對齊邊界再取最大值 得到結構體的對齊邊界

 

1.存儲結構體的起始地址是對齊邊界的倍數,這里的對齊邊界是這個類型本身的對齊邊界,不是結構體的對齊邊界(取邊界的最大值)

2.結構體整體占用字節數需要是對齊邊界的整數倍,不夠的需要擴展

實例:

package main
import (
    "fmt"
    "unsafe"
)
type Part1 struct {
    a bool        //1
    b int32       //4
    c int8        //1
    d int64       //8
    e byte        //1
}
//分析 [1 0 0 0][1 1 1 1][1 0 0 0 0 0 0 0][1 1 1 1 1 1 1 1][1]
//為8的倍數 [1 0 0 0][1 1 1 1][1 0 0 0 0 0 0 0][1 1 1 1 1 1 1 1][1 0 0 0 0 0 0 0] //32 func main() { part1 :
= Part1{} fmt.Printf("part1 size: %d, align: %d\n", unsafe.Sizeof(part1), unsafe.Alignof(part1)) }

 

結果:

part1 size: 32, align: 8

 

分析:

成員對齊

  • 第一個成員 a
    • 類型為 bool
    • 大小/對齊值為 1 字節
    • 初始地址,偏移量為 0。占用了第 1 位
  • 第二個成員 b
    • 類型為 int32
    • 大小/對齊值為 4 字節
    • 根據規則 1,其偏移量必須為 4 的整數倍。確定偏移量為 4,因此 2-4 位為 Padding。而當前數值從第 5 位開始填充,到第 8 位。如下:axxx|bbbb
  • 第三個成員 c
    • 類型為 int8
    • 大小/對齊值為 1 字節
    • 根據規則1,其偏移量必須為 1 的整數倍。當前偏移量為 8。不需要額外對齊,填充 1 個字節到第 9 位。如下:axxx|bbbb|c…
  • 第四個成員 d
    • 類型為 int64
    • 大小/對齊值為 8 字節
    • 根據規則 1,其偏移量必須為 8 的整數倍。確定偏移量為 16,因此
      9-16 位為 Padding。而當前數值從第 17 位開始寫入,到第 24 位。如下:axxx|bbbb|cxxx|xxxx|dddd|dddd
  • 第五個成員 e
    • 類型為 byte
    • 大小/對齊值為 1 字節
    • 根據規則 1,其偏移量必須為 1 的整數倍。當前偏移量為 24。不需要額外對齊,填充 1 個字節到第 25 位。如下:axxx|bbbb|cxxx|xxxx|dddd|dddd|e…

整體對齊

在每個成員變量進行對齊后,根據規則 2,整個結構體本身也要進行字節對齊,因為可發現它可能並不是 2^n,不是偶數倍。顯然不符合對齊的規則

根據規則 2,可得出對齊值為 8。現在的偏移量為 25,不是 8 的整倍數。因此確定偏移量為 32。對結構體進行對齊

結果

Part1 內存布局:axxx|bbbb|cxxx|xxxx|dddd|dddd|exxx|xxxx

小結

通過本節的分析,可得知先前的 “推算” 為什么錯誤?

是因為實際內存管理並非 “一個蘿卜一個坑” 的思想。而是一塊一塊。通過空間換時間(效率)的思想來完成這塊讀取、寫入。另外也需要兼顧不同平台的內存操作情況

巧妙的結構體

在上一小節,可得知根據成員變量的類型不同,其結構體的內存會產生對齊等動作。那假設字段順序不同,會不會有什么變化呢?我們一起來試試吧 :-)

package main

import (
   "fmt"
   "unsafe"
)

type Part1 struct {
   a bool   //1
   b int32  //4
   c int8   //1
   d int64  //8
   e byte   //1
}
//分析 [1 0 0 0][1 1 1 1][1 0 0 0 0 0 0 0][1 1 1 1 1 1 1 1][1]
//為8的倍數 [1 0 0 0][1 1 1 1][1 0 0 0 0 0 0 0][1 1 1 1 1 1 1 1][1 0 0 0 0 0 0 0] //32
type Part2
struct { e byte //1 c int8 //1 a bool //1 b int32 //4 d int64 //8 } //分析 [1][1][1 0][1 1 1 1][1 1 1 1 1 1 1 1] //16 也剛好為8的倍數

func main() { part1 :
= Part1{} part2 := Part2{} fmt.Printf("part1 size: %d, align: %d\n", unsafe.Sizeof(part1), unsafe.Alignof(part1)) fmt.Printf("part2 size: %d, align: %d\n", unsafe.Sizeof(part2), unsafe.Alignof(part2)) }

輸出結果:

part1 size: 32, align: 8
part2 size: 16, align: 8

通過結果可以驚喜的發現,只是 “簡單” 對成員變量的字段順序進行改變,就改變了結構體占用大小

接下來我們一起剖析一下 Part2,看看它的內部到底和上一位之間有什么區別,才導致了這樣的結果?

分析流程

 

 

成員對齊

  • 第一個成員 e
    • 類型為 byte
    • 大小/對齊值為 1 字節
    • 初始地址,偏移量為 0。占用了第 1 位
  • 第二個成員 c
    • 類型為 int8
    • 大小/對齊值為 1 字節
    • 根據規則1,其偏移量必須為 1 的整數倍。當前偏移量為 2。不需要額外對齊
  • 第三個成員 a
    • 類型為 bool
    • 大小/對齊值為 1 字節
    • 根據規則1,其偏移量必須為 1 的整數倍。當前偏移量為 3。不需要額外對齊
  • 第四個成員 b
    • 類型為 int32
    • 大小/對齊值為 4 字節
    • 根據規則1,其偏移量必須為 4 的整數倍。確定偏移量為 4,因此第 3 位為 Padding。而當前數值從第 4 位開始填充,到第 8 位。如下:ecax|bbbb
  • 第五個成員 d
    • 類型為 int64
    • 大小/對齊值為 8 字節
    • 根據規則1,其偏移量必須為 8 的整數倍。當前偏移量為 8。不需要額外對齊,從 9-16 位填充 8 個字節。如下:ecax|bbbb|dddd|dddd

整體對齊

符合規則 2,不需要額外對齊

結果

Part2 內存布局:ecax|bbbb|dddd|dddd

總結

通過對比 Part1 和 Part2 的內存布局,你會發現兩者有很大的不同。如下:

  • Part1:axxx|bbbb|cxxx|xxxx|dddd|dddd|exxx|xxxx
  • Part2:ecax|bbbb|dddd|dddd

仔細一看,Part1 存在許多 Padding。顯然它占據了不少空間,那么 Padding 是怎么出現的呢?

通過本文的介紹,可得知是由於不同類型導致需要進行字節對齊,以此保證內存的訪問邊界

那么也不難理解,為什么調整結構體內成員變量的字段順序就能達到縮小結構體占用大小的疑問了,是因為巧妙地減少了 Padding 的存在。讓它們更 “緊湊” 了。這一點對於加深 Go 的內存布局印象和大對象的優化非常有幫

當然了,沒什么特殊問題,你可以不關注這一塊。但你要知道這塊知識點

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM