golang內存對齊

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"0x00 面試題"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面是之前的小弟去面試騰訊電競碰到的面試題:"}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"type S struct {\n\tA uint32\n\tB uint64\n\tC uint64\n\tD uint64\n\tE struct{}\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面的"},{"type":"codeinline","content":[{"type":"text","text":"struct S"}]},{"type":"text","text":",佔用多大的內存?首先,我們可以明確"},{"type":"codeinline","content":[{"type":"text","text":"S"}]},{"type":"text","text":"的是8字節對齊的,因此我給出的答案是32。但是很明顯,答案並不是32。先看下正確答案:"}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"func main() {\n\tfmt.Println(unsafe.Offsetof(S{}.E))\n\tfmt.Println(unsafe.Sizeof(S{}.E))\n\tfmt.Println(unsafe.Sizeof(S{}))\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"終端輸出:"}]},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"32\n0\n40"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到,"},{"type":"codeinline","content":[{"type":"text","text":"S.E"}]},{"type":"text","text":"的偏移量offset是32,並且size確實是0,但是S實例的size卻是40。說明S.E後面隱藏着一個8字節的padding。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"0x01 社區解答"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那爲什麼需要這個padding呢?在github上面查到了相關的"},{"type":"link","attrs":{"href":"https://github.com/golang/go/issues/38194","title":null},"content":[{"type":"text","text":"issue"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":",看見社區大佬的回覆是這樣的:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Trailing zero-sized struct fields are padded because if they weren’t, &C.E would point to an invalid memory location."}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"結構體尾部size爲0的變量(字段)會被分配內存空間進行填充,原因是如果不給它分配內存,該變量(字段)指針將指向一個非法的內存空間(類似C/C++的野指針)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"並且還給了個相關的"},{"type":"link","attrs":{"href":"https://github.com/golang/go/issues/9401","title":null},"content":[{"type":"text","text":"issue"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":"地址:"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"If a non-zero-size struct contains a final zero-size field f, the address &x.f may point beyond the allocation for the struct. This could cause a memory leak or a crash in the garbage collector (invalid pointer found)."}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"一個非空結構體包含有尾部size爲0的變量(字段),如果不給它分配內存,那麼該變量(字段)的指針地址將指向一個超出該結構體內存範圍的內存空間。這可能會導致內存泄漏,或者在內存垃圾回收過程中,程序crash掉。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"0x02 原理"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1. 術語"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"字(word)"}]}]}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"是用於表示其自然的數據單位,也叫"},{"type":"codeinline","content":[{"type":"text","text":"machine word"}]},{"type":"text","text":"。字是電腦用來一次性處理事務的一個固定長度。"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"字長"}]}]}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一個字的位數,現代電腦的字長通常爲 16、32、64 位。(一般 N 位系統的字長是 "},{"type":"codeinline","content":[{"type":"text","text":"N/8"}]},{"type":"text","text":" 字節。)"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2. 爲什麼要對齊"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"操作系統並非一個字節一個字節訪問內存,而是按"},{"type":"codeinline","content":[{"type":"text","text":"2, 4, 8"}]},{"type":"text","text":"這樣的字長來訪問。因此,當CPU從存儲器讀數據到寄存器,或者從寄存器寫數據到存儲器,IO的數據長度通常是字長。如 32 位系統訪問粒度是 4 字節(bytes),64 位系統的是 8 字節。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當被訪問的數據長度爲 "},{"type":"codeinline","content":[{"type":"text","text":"n"}]},{"type":"text","text":" 字節且該數據地址爲"},{"type":"codeinline","content":[{"type":"text","text":"n"}]},{"type":"text","text":"字節對齊,那麼操作系統就可以高效地一次定位到數據,無需多次讀取、處理對齊運算等額外操作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"數據結構應該儘可能地在自然邊界上對齊。如果訪問未對齊的內存,CPU需要做兩次內存訪問。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3. 數據結構對齊"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"看下go官方文檔 "},{"type":"link","attrs":{"href":"https://golang.org/ref/spec#Size_and_alignment_guarantees","title":null},"content":[{"type":"text","text":"Size and alignment guarantees"}]},{"type":"text","text":" 對於go數據類型的"},{"type":"text","marks":[{"type":"strong"}],"text":"大小保證"},{"type":"text","text":"和"},{"type":"text","marks":[{"type":"strong"}],"text":"對齊保證"},{"type":"text","text":":"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b6/b65202f67083f938c8ac01ec2a17787a.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"大小保證"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在Go中,如果兩個值的類型爲同一種類的類型,並且它們的類型的種類不爲字符串、接口、數組和結構體,則這兩個值的尺寸總是相等的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"目前(Go 1.14),至少對於官方標準編譯器來說,任何一個特定類型的所有值的尺寸都是相同的。所以我們也常說一個值的尺寸爲此值的類型的尺寸。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下表列出了各種種類的類型的尺寸(對標準編譯器1.14來說):"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/dc/dc0036083f4e92f2366245077bd01642.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一個結構體類型的尺寸取決於它的各個字段的類型尺寸和這些字段的排列順序。 爲了程序執行性能,編譯器需要保證某些類型的值在內存中存放時必須滿足特定的內存地址對齊要求。 地址對齊可能會造成相鄰的兩個字段之間在內存中被插入填充一些多餘的字節。 所以,一個結構體類型的尺寸必定不小於(常常會大於)此結構體類型的各個字段的類型尺寸之和。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一個數組類型的尺寸取決於它的元素類型的尺寸和它的長度。它的尺寸爲它的元素類型的尺寸和它的長度的乘積。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"struct{}"}]},{"type":"text","text":" 和"},{"type":"codeinline","content":[{"type":"text","text":"[0]T{}"}]},{"type":"text","text":" 的大小爲 0; 不同的大小爲 0 的變量可能指向同一塊地址。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"對齊保證"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"go官方文檔中的對"},{"type":"text","marks":[{"type":"strong"}],"text":"對齊保證"},{"type":"text","text":"的要求只有如下解釋:"}]},{"type":"blockquote","content":[{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"對於任何類型的變量"},{"type":"codeinline","content":[{"type":"text","text":"x"}]},{"type":"text","text":","},{"type":"codeinline","content":[{"type":"text","text":"unsafe.Alignof(x)"}]},{"type":"text","text":"的結果最小爲"},{"type":"codeinline","content":[{"type":"text","text":"1"}]},{"type":"text","text":"。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"對於一個結構體類型的變量"},{"type":"codeinline","content":[{"type":"text","text":"x"}]},{"type":"text","text":","},{"type":"codeinline","content":[{"type":"text","text":"unsafe.Alignof(x)"}]},{"type":"text","text":"的結果爲"},{"type":"codeinline","content":[{"type":"text","text":"x"}]},{"type":"text","text":"的所有字段的對齊保證"},{"type":"codeinline","content":[{"type":"text","text":"unsafe.Alignof(x.f)"}]},{"type":"text","text":"中的最大值(但是最小爲"},{"type":"codeinline","content":[{"type":"text","text":"1"}]},{"type":"text","text":")。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"對於一個數組類型的變量"},{"type":"codeinline","content":[{"type":"text","text":"x"}]},{"type":"text","text":","},{"type":"codeinline","content":[{"type":"text","text":"unsafe.Alignof(x)"}]},{"type":"text","text":"的結果和此數組的元素類型的一個變量的對齊保證相等。"}]}]}]}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/49/4902edfd5a8ccb585c323b14b1a676d3.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"類型對齊保證也稱爲值地址對齊保證。 如果一個類型"},{"type":"codeinline","content":[{"type":"text","text":"T"}]},{"type":"text","text":"的對齊保證爲"},{"type":"codeinline","content":[{"type":"text","text":"N"}]},{"type":"text","text":"(一個正整數),則在運行時刻"},{"type":"codeinline","content":[{"type":"text","text":"T"}]},{"type":"text","text":"類型的每個(可尋址的)值的地址都是"},{"type":"codeinline","content":[{"type":"text","text":"N"}]},{"type":"text","text":"的倍數。 我們也可以說類型"},{"type":"codeinline","content":[{"type":"text","text":"T"}]},{"type":"text","text":"的值的地址保證爲"},{"type":"codeinline","content":[{"type":"text","text":"N"}]},{"type":"text","text":"字節對齊的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"事實上,每個類型有兩個對齊保證。當它被用做結構體類型的字段類型時的對齊保證稱爲此類型的字段對齊保證,其它情形的對齊保證稱爲此類型的一般對齊保證。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於一個類型"},{"type":"codeinline","content":[{"type":"text","text":"T"}]},{"type":"text","text":",我們可以調用"},{"type":"codeinline","content":[{"type":"text","text":"unsafe.Alignof(t)"}]},{"type":"text","text":"來獲得它的一般對齊保證,其中"},{"type":"codeinline","content":[{"type":"text","text":"t"}]},{"type":"text","text":"爲一個"},{"type":"codeinline","content":[{"type":"text","text":"T"}]},{"type":"text","text":"類型的非字段值, 也可以調用"},{"type":"codeinline","content":[{"type":"text","text":"unsafe.Alignof(x.t)"}]},{"type":"text","text":"來獲得"},{"type":"codeinline","content":[{"type":"text","text":"T"}]},{"type":"text","text":"的字段對齊保證,其中"},{"type":"codeinline","content":[{"type":"text","text":"x"}]},{"type":"text","text":"爲一個結構體值並且"},{"type":"codeinline","content":[{"type":"text","text":"t"}]},{"type":"text","text":"爲一個類型爲"},{"type":"codeinline","content":[{"type":"text","text":"T"}]},{"type":"text","text":"的結構體字段值。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在運行時刻,對於類型爲"},{"type":"codeinline","content":[{"type":"text","text":"T"}]},{"type":"text","text":"的一個值"},{"type":"codeinline","content":[{"type":"text","text":"t"}]},{"type":"text","text":",我們可以調用"},{"type":"codeinline","content":[{"type":"text","text":"reflect.TypeOf(t).Align()"}]},{"type":"text","text":"來獲得類型"},{"type":"codeinline","content":[{"type":"text","text":"T"}]},{"type":"text","text":"的一般對齊保證, 也可以調用"},{"type":"codeinline","content":[{"type":"text","text":"reflect.TypeOf(t).FieldAlign()"}]},{"type":"text","text":"來獲得"},{"type":"codeinline","content":[{"type":"text","text":"T"}]},{"type":"text","text":"的字段對齊保證。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於當前的官方Go編譯器(1.14版本),一個類型的一般對齊保證和字段對齊保證總是相等的。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"重排優化"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"看下下面的例子:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/28/28d0842edb1ea66d65f98c5be06a44cf.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"T1,T2"}]},{"type":"text","text":"內字段最大的都是"},{"type":"codeinline","content":[{"type":"text","text":"int64"}]},{"type":"text","text":", 大小爲 8bytes,對齊按機器字確定,64 位下是 8bytes,所以將按 8bytes 對齊"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"T1.a"}]},{"type":"text","text":" 大小 2bytes, 填充 6bytes 使對齊(後邊字段已對齊,所以直接填充)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"T1.b"}]},{"type":"text","text":" 大小 8bytes, 已對齊"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"T1.c"}]},{"type":"text","text":" 大小 2bytes,填充 6bytes 使對齊(後邊無字段,所以直接填充)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"總大小爲 "},{"type":"codeinline","content":[{"type":"text","text":"8+8+8=24"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"T2"}]},{"type":"text","text":"中將"},{"type":"codeinline","content":[{"type":"text","text":"c"}]},{"type":"text","text":"提前後,"},{"type":"codeinline","content":[{"type":"text","text":"a"}]},{"type":"text","text":"和"},{"type":"codeinline","content":[{"type":"text","text":"c"}]},{"type":"text","text":"總大小 4bytes,在填充 4bytes 使對齊"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"總大小爲 "},{"type":"codeinline","content":[{"type":"text","text":"8+8=16"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以,"},{"type":"text","marks":[{"type":"strong"}],"text":"合理重排字段可以減少填充,使 struct 字段排列更緊密"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"零大小字段對齊"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"零大小字段("},{"type":"codeinline","content":[{"type":"text","text":"zero sized field"}]},{"type":"text","text":")是指"},{"type":"codeinline","content":[{"type":"text","text":"struct{}"}]},{"type":"text","text":",大小爲 0,按理作爲字段時不需要對齊,但當在作爲結構體最後一個字段("},{"type":"codeinline","content":[{"type":"text","text":"final field"}]},{"type":"text","text":")時需要對齊的。即開篇我們講到的面試題的情況,假設有指針指向這個"},{"type":"codeinline","content":[{"type":"text","text":"final zero field"}]},{"type":"text","text":", 返回的地址將在結構體之外(即指向了別的內存),如果此指針一直存活不釋放對應的內存,就會有內存泄露的問題(該內存不因結構體釋放而釋放),go會對這種"},{"type":"codeinline","content":[{"type":"text","text":"final zero field"}]},{"type":"text","text":"也做填充,使對齊。當然,有一種情況不需要對這個"},{"type":"codeinline","content":[{"type":"text","text":"final zero field"}]},{"type":"text","text":"做額外填充,也就是這個末尾的上一個字段未對齊,需要對這個字段進行填充時,"},{"type":"codeinline","content":[{"type":"text","text":"final zero field"}]},{"type":"text","text":"就不需要再次填充,而是直接利用了上一個字段的填充。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/35/35776f37556b36717f0326f839a25412.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"64 位字安全訪問保證"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 32 位系統上想要原子操作 64 位字(如 uint64)的話,需要由調用方保證其數據地址是 64 位對齊的,否則原子訪問會有異常。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"拿"},{"type":"codeinline","content":[{"type":"text","text":"uint64"}]},{"type":"text","text":"來說,大小爲 8bytes,32 位系統上按 4字節 對齊,64 位系統上按 8字節對齊。在 64 位系統上,8bytes 剛好和其字長相同,所以可以一次完成原子的訪問,不被其他操作影響或打斷。而 32 位系統,4byte 對齊,字長也爲 4bytes,可能出現"},{"type":"codeinline","content":[{"type":"text","text":"uint64"}]},{"type":"text","text":"的數據分佈在"},{"type":"text","marks":[{"type":"strong"}],"text":"兩個數據塊"},{"type":"text","text":"中,需要兩次操作才能完成訪問。如果兩次操作中間有可能別其他操作修改,不能保證原子性。這樣的訪問方式也是不安全的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"如何保證呢"},{"type":"text","text":"?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"變量或開闢的結構體、數組和切片值中的第一個 64 位字可以被認爲是 8 字節對齊"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"開闢"},{"type":"text","text":"的意思是通過聲明,make,new 方式創建的,就是說這樣創建的 64 位字可以保證是 64 位對齊的。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"4. 總結"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"內存對齊是爲了讓 "},{"type":"text","marks":[{"type":"strong"}],"text":"cpu"},{"type":"text","text":" 更高效訪問內存中數據"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"struct 的對齊是:如果類型 t 的對齊保證是 n,那麼類型 t 的每個值的"},{"type":"text","marks":[{"type":"strong"}],"text":"地址"},{"type":"text","text":"在運行時必須是 n 的倍數。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"即 "},{"type":"codeinline","content":[{"type":"text","text":"uintptr(unsafe.Pointer(&x)) % unsafe.Alignof(x) == 0"}]}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"struct 內字段如果填充過多,可以嘗試重排,使字段排列更緊密,減少內存浪費"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"零大小字段要避免作爲 struct 最後一個字段,會有內存浪費"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"32 位系統上對 64 位字的原子訪問要保證其是 8bytes 對齊的;當然如果不必要的話,還是用加鎖("},{"type":"codeinline","content":[{"type":"text","text":"mutex"}]},{"type":"text","text":")的方式更清晰簡單"}]}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"0x03 拓展知識"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"依舊是開篇的例子,假設現在,連續聲明瞭兩個S的變量s1, s2,那s2的內存地址會是怎樣的呢?"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/72/725689a387593928118fcaa6ccc490f6.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲s1和s2在內存上是連續的,我們也已知s1的大小是40,那爲什麼s2的地址相對s1的地址偏移量卻是48呢?"},{"type":"codeinline","content":[{"type":"text","text":"(0xc0000aa090-0xc0000aa060 = 48)"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先給出答案,因爲對於s1這個對象,它的大小是40bytes,而go在內存分配時,會從span中拿大於或等於40的最小的span中的一個塊給這個對象,而"},{"type":"codeinline","content":[{"type":"text","text":"sizeclass"}]},{"type":"text","text":"中這個塊的大小值爲48。所以雖然s1的大小是40bytes,但實際分配給這個對象的內存大小是48。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"go的內存分配,首先是按照"},{"type":"codeinline","content":[{"type":"text","text":"sizeclass"}]},{"type":"text","text":"劃分span,然後每個"},{"type":"codeinline","content":[{"type":"text","text":"span"}]},{"type":"text","text":"中的"},{"type":"codeinline","content":[{"type":"text","text":"page"}]},{"type":"text","text":"又分成一個個小格子:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"span"}]},{"type":"text","text":"是golang內存管理的基本單位,是由一片連續的"},{"type":"codeinline","content":[{"type":"text","text":"8KB"}]},{"type":"text","text":"(golang "},{"type":"codeinline","content":[{"type":"text","text":"page"}]},{"type":"text","text":"的大小)的頁組成的大塊內存。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如下圖,"},{"type":"codeinline","content":[{"type":"text","text":"span"}]},{"type":"text","text":"由一組連續的頁組成,按照一定大小劃分成"},{"type":"codeinline","content":[{"type":"text","text":"object"}]},{"type":"text","text":"。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e0/e031fbbc1d28630df05b275611103561.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每個"},{"type":"codeinline","content":[{"type":"text","text":"span"}]},{"type":"text","text":"管理指定規格(以golang 中的 page爲單位)的內存塊,內存池分配出不同規格的內存塊就是通過span體現出來的,應用程序創建對象就是通過找到對應規格的"},{"type":"codeinline","content":[{"type":"text","text":"span"}]},{"type":"text","text":"來存儲的,下面是 "},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":"結構中的主要部分。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ab/abc7e47d051ae3b23cdff4575aba5311.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼要想區分不同規格的"},{"type":"codeinline","content":[{"type":"text","text":"span"}]},{"type":"text","text":",必須要有一個標識,每個"},{"type":"codeinline","content":[{"type":"text","text":"span"}]},{"type":"text","text":"通過"},{"type":"codeinline","content":[{"type":"text","text":"spanclass"}]},{"type":"text","text":"標識屬於哪種規格的"},{"type":"codeinline","content":[{"type":"text","text":"span"}]},{"type":"text","text":",golang的"},{"type":"codeinline","content":[{"type":"text","text":"span"}]},{"type":"text","text":"規格一共有67種,具體如下:"}]},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"// go/src/runtime/sizeclasses.go\n\n// class: \t\t\t class ID,每個span結構中都有一個class ID, 表示該span可處理的對象類型\n// bytes/obj:\t\t 該class代表對象的字節數\n// bytes/span:\t 每個span佔用堆的字節數,也即頁數*頁大小\n// objects: \t\t 每個span可分配的對象個數,也即(bytes/spans)/(bytes/obj)\n// waste bytes: \t每個span產生的內存碎片,也即(bytes/spans)%(bytes/obj)\n\n// class bytes/obj bytes/span objects tail waste max waste\n// 1 8 8192 1024 0 87.50%\n// 2 16 8192 512 0 43.75%\n// 3 32 8192 256 0 46.88%\n// 4 48 8192 170 32 31.52% 8192-(48*170)\n// 5 64 8192 128 0 23.44%\n// 6 80 8192 102 32 19.07%\n// 7 96 8192 85 32 15.95%\n// 8 112 8192 73 16 13.56%\n// 9 128 8192 64 0 11.72%\n// 10 144 8192 56 128 11.82%\n// 11 160 8192 51 32 9.73%\n// 12 176 8192 46 96 9.59%\n// 13 192 8192 42 128 9.25%\n// 14 208 8192 39 80 8.12%\n// 15 224 8192 36 128 8.15%\n// 16 240 8192 34 32 6.62%\n// 17 256 8192 32 0 5.86%\n// 18 288 8192 28 128 12.16%\n// 19 320 8192 25 192 11.80%\n// 20 352 8192 23 96 9.88%\n// 21 384 8192 21 128 9.51%\n// 22 416 8192 19 288 10.71%\n// 23 448 8192 18 128 8.37%\n// 24 480 8192 17 32 6.82%\n// 25 512 8192 16 0 6.05%\n// 26 576 8192 14 128 12.33%\n// 27 640 8192 12 512 15.48%\n// 28 704 8192 11 448 13.93%\n// 29 768 8192 10 512 13.94%\n// 30 896 8192 9 128 15.52%\n// 31 1024 8192 8 0 12.40%\n// 32 1152 8192 7 128 12.41%\n// 33 1280 8192 6 512 15.55%\n// 34 1408 16384 11 896 14.00%\n// 35 1536 8192 5 512 14.00%\n// 36 1792 16384 9 256 15.57%\n// 37 2048 8192 4 0 12.45%\n// 38 2304 16384 7 256 12.46%\n// 39 2688 8192 3 128 15.59%\n// 40 3072 24576 8 0 12.47%\n// 41 3200 16384 5 384 6.22%\n// 42 3456 24576 7 384 8.83%\n// 43 4096 8192 2 0 15.60%\n// 44 4864 24576 5 256 16.65%\n// 45 5376 16384 3 256 10.92%\n// 46 6144 24576 4 0 12.48%\n// 47 6528 32768 5 128 6.23%\n// 48 6784 40960 6 256 4.36%\n// 49 6912 49152 7 768 3.37%\n// 50 8192 8192 1 0 15.61%\n// 51 9472 57344 6 512 14.28%\n// 52 9728 49152 5 512 3.64%\n// 53 10240 40960 4 0 4.99%\n// 54 10880 32768 3 128 6.24%\n// 55 12288 24576 2 0 11.45%\n// 56 13568 40960 3 256 9.99%\n// 57 14336 57344 4 0 5.35%\n// 58 16384 16384 1 0 12.49%\n// 59 18432 73728 4 0 11.11%\n// 60 19072 57344 3 128 3.57%\n// 61 20480 40960 2 0 6.87%\n// 62 21760 65536 3 256 6.25%\n// 63 24576 24576 1 0 11.45%\n// 64 27264 81920 3 128 10.00%\n// 65 28672 57344 2 0 4.91%\n// 66 32768 32768 1 0 12.50%"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每個"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":"按照它自身的屬性"},{"type":"codeinline","content":[{"type":"text","text":"Size Class"}]},{"type":"text","text":"的大小分割成若干個"},{"type":"codeinline","content":[{"type":"text","text":"object"}]},{"type":"text","text":",每個"},{"type":"codeinline","content":[{"type":"text","text":"object"}]},{"type":"text","text":"可存儲一個對象。並且會使用一個位圖來標記其尚未使用的"},{"type":"codeinline","content":[{"type":"text","text":"object"}]},{"type":"text","text":"。屬性"},{"type":"codeinline","content":[{"type":"text","text":"Size Class"}]},{"type":"text","text":"決定"},{"type":"codeinline","content":[{"type":"text","text":"object"}]},{"type":"text","text":"大小,而"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":"只會分配給和"},{"type":"codeinline","content":[{"type":"text","text":"object"}]},{"type":"text","text":"尺寸大小接近的對象,當然,對象的大小要小於"},{"type":"codeinline","content":[{"type":"text","text":"object"}]},{"type":"text","text":"大小。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章