[問題]
在開始之前颖杏,先給大家看下go的各種基本類型在內存中所占大小情況:
func main() {
fmt.Printf("bool size: %d\n", unsafe.Sizeof(bool(true)))
fmt.Printf("int32 size: %d\n", unsafe.Sizeof(int32(0)))
fmt.Printf("int8 size: %d\n", unsafe.Sizeof(int8(0)))
fmt.Printf("int64 size: %d\n", unsafe.Sizeof(int64(0)))
fmt.Printf("byte size: %d\n", unsafe.Sizeof(byte(0)))
fmt.Printf("string size: %d\n", unsafe.Sizeof("EDDYCJY"))
}
輸出結果:
bool size: 1 //go中申請內存單位必須是2的整數(shù)次冪,最小內存單位為2^0=1byte
int32 size: 4
int8 size: 1
int64 size: 8
byte size: 1
string size: 16
有了以上結果坛芽,下面請大家計算一下留储,Part1結構體在內存中占空間大小為?
type Part1 struct {
a bool
b int32
c int8
d int64
e byte
}
輸出結果:
func main() {
part1 := Part1{}
fmt.Printf("part1 size: %d, align: %d\n", unsafe.Sizeof(part1), unsafe.Alignof(part1))
}
//output:part1 size: 32, align: 8
最終輸出為32字節(jié)咙轩,與預期的 1+4+1+8+1=15不符获讳,為何?其實是編譯器在編譯階段會對它進行內存對齊而導致的活喊,要想理解這其中的奧秘丐膝,則需要認識什么是“內存對齊”了。
什么是內存對齊
在講什么是內存對齊之前,我們先來看個例子:
現(xiàn)有一片內存空間帅矗,首地址偏移量為0偎肃,其他采用相對地址。假設需要讀取右圖陰影部分字節(jié)數(shù)據(jù)损晤,其訪問過程如下:
- 首次讀取第一個內存塊 0-3 软棺,移除多余的0字節(jié)
- 讀取第二個內存塊 4-7 ,移除多余的5-7字節(jié)
- 合并1-4字節(jié)尤勋,讀出 xing.v 值,存入寄存器
對內存進行對齊后茵宪,內存地址分布為:
此時訪問目標數(shù)據(jù)只需要 首地址+4*(2-1)即可最冰,效率提升了一半呢。
看完例子稀火,總結一下:內存對齊就是指編譯器在編譯階段對需要申請的內存地址(虛擬內存地址)進行處理(高位補零填充)暖哨,使cpu訪問任意內存數(shù)據(jù)都是一次訪問即可,提高訪存效率凰狞。
為什么要內存對齊
平臺兼容和移植性考慮(程序代碼通用性)
不是所有的硬件平臺都能訪問任意地址上的任意數(shù)據(jù)的篇裁,例如特定硬件平臺只允許在特定地址獲取特定類型的數(shù)據(jù),否則會導致異常情況赡若。性能原因(對cpu达布、內存方面有一定要求的話)
若訪問未對齊的內存,將會導致cpu進行倆次內存訪問逾冬,并且需要花費額外的時鐘周期來處理對齊及運算黍聂。而對于已經(jīng)對齊的內存來說,只需一次訪問即可完成讀取指令動作身腻,效率大大提升产还。某些硬件平臺(例如ARM)體系不支持未對齊的內存訪問
默認對齊系數(shù)
不同的平臺的編譯器都有自己默認的"對齊系數(shù)",可通過預編譯命令 #pragma pack(n) 更改對齊系數(shù),n代表想指定的系數(shù)大小嘀趟。一般常用的平臺系數(shù)如下:
- 32位:4 字節(jié)
- 64位:8 字節(jié)
如何對齊脐区?
首先查看每種類型的對齊系數(shù):
func main() {
fmt.Printf("bool \talign: %d\n", unsafe.Alignof(true))
fmt.Printf("int32 \talign: %d\n", unsafe.Alignof(int32(0)))
fmt.Printf("int8 \talign: %d\n", unsafe.Alignof(int8(0)))
fmt.Printf("int64 \talign: %d\n", unsafe.Alignof(int64(0)))
fmt.Printf("byte \talign: %d\n", unsafe.Alignof(byte(0)))
fmt.Printf("string \talign: %d\n", unsafe.Alignof("EDDYCJY"))
fmt.Printf("map \talign: %d\n", unsafe.Alignof(map[string]string{}))
}
輸出
//單位: byte
bool align: 1
int32 align: 4
int8 align: 1
int64 align: 8
byte align: 1
string align: 8
map align: 8
slice align: 8
golang可以使用unsafe.Alignof 查看每種類型的對齊系數(shù)。對齊系數(shù)一般為2^n次方她按,但由于類型的對齊系數(shù)不能超過默認對齊系數(shù)(電腦64win牛隅,默認系數(shù)=8),所以最大值為8.
[成員對齊規(guī)則]
1結構體變量首地址必須模運算對齊系數(shù)等于0,必須是對齊系數(shù)(也叫對齊邊界)的整數(shù)倍.
2.第一個成員變量的偏移量為0尤溜,往后的每個成員變量地址使用相對地址倔叼,對齊值必須為 編譯器對齊系數(shù)和成員變量類型對齊系數(shù)的最小值,成員變量偏移地址必須為對齊系數(shù)的整數(shù)倍
[結構體對齊規(guī)則]
除了結構體成員需要做對齊外宫莱,結構體本身也需要對齊丈攒,即所得最后的內存大小必須為對齊系數(shù)的整數(shù)倍,例如64位機器上,結構體成員對齊后大小位21字節(jié)巡验,在對結構體對齊時需要填充3字節(jié)际插,使得Sizeof(struct) 為對齊系數(shù)整數(shù)倍
最后
內存對齊主要是編譯器做的事情,對用戶代碼來說是透明的显设,某種層面來講框弛,程序員可以不關心這個。但作為一名專業(yè)過硬的程序員捕捂,去了解內存對齊很有必要瑟枫,在性能調優(yōu)場景,或者是海量數(shù)據(jù)場景 亦或是對于內存敏感的應用指攒,合理調整結構體的成員位置慷妙,分分鐘可以節(jié)省很多內存空間。(所以允悦,開篇提到的Part1結構體膝擂,明白為什么內存大小是32字節(jié)了嗎?)