1.平臺原因(移植原因): 不是所有的硬件平臺都能訪問任意地址上的任意數(shù)據(jù)的;某些硬件平臺只能

成都創(chuàng)新互聯(lián)專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務,包含不限于成都網(wǎng)站設計、成都網(wǎng)站制作、外貿網(wǎng)站建設、景德鎮(zhèn)網(wǎng)絡推廣、微信小程序定制開發(fā)、景德鎮(zhèn)網(wǎng)絡營銷、景德鎮(zhèn)企業(yè)策劃、景德鎮(zhèn)品牌公關、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運營等,從售前售中售后,我們都將竭誠為您服務,您的肯定,是我們最大的嘉獎;成都創(chuàng)新互聯(lián)為所有大學生創(chuàng)業(yè)者提供景德鎮(zhèn)建站搭建服務,24小時服務熱線:18980820575,官方網(wǎng)址:chinadenli.net
在某些地址處取某些特定類型的數(shù)據(jù),否則拋出硬件異常。
2.性能原因: 數(shù)據(jù)結構應該盡可能地在自然邊界上對齊。原因在于,為了訪問未對齊的內存,處理器需要作兩次內存訪問;而對齊的內存訪問僅需要一次訪問。(如果是對齊的,那么CPU不需要跨越兩個操作字,不是對齊的則需要訪問兩個操作字才能拼接出需要的內存地址)
指針的大小一般是一個機器字的大小
通過Go語言的structlayout工具,可以得出下圖
這些類型在之前的 slice 、 map 、 interface 已經(jīng)介紹過了,也特意強調過,makehmap函數(shù)返回的是一個指針,因此map的對齊為一個機器字.
回頭看看 sync.pool的防止copy的空結構體字段,也是放在第一位,破案了。
計算機結構可能會要求內存地址 進行對齊;也就是說,一個變量的地址是一個因子的倍數(shù),也就是該變量的類型是對齊值。
函數(shù)Alignof接受一個表示任何類型變量的表達式作為參數(shù),并以字節(jié)為單位返回變量(類型)的對齊值。對于變量x:
這是因為int64在bool之后未對齊。
它是32位對齊的,但不是64位對齊的,因為我們使用的是32位系統(tǒng),因此實際上只是兩個32位值并排在一起。
● 內存對齊是為了cpu更高效訪問內存中數(shù)據(jù)
● 結構體對齊依賴類型的大小保證和對齊保證
● 地址對齊保證是:如果類型 t 的對齊保證是 n,那么類型 t 的每個值的地址在運行時必須是 n 的倍數(shù)。
● struct內字段如果填充過多,可以嘗試重排,使字段排列更緊密,減少內存浪費
● 零大小字段要避免作為struct最后一個字段,會有內存浪費
● 32位系統(tǒng)上對64位字的原子訪問要保證其是8bytes對齊的;當然如果不必要的 話,還是用加鎖(mutex)的方式更清晰簡單
圖解go-內存對齊
doc-pdf
在開始之前,希望你計算一下 Part1 共占用的大小是多少呢?
輸出結果:
這么一算, Part1 這一個結構體的占用內存大小為 1+4+1+8+1 = 15 個字節(jié)。相信有的小伙伴是這么算的,看上去也沒什么毛病
真實情況是怎么樣的呢?我們實際調用看看,如下:
輸出結果:
最終輸出為占用 32 個字節(jié)。這與前面所預期的結果完全不一樣。這充分地說明了先前的計算方式是錯誤的。為什么呢?
在這里要提到 “內存對齊” 這一概念,才能夠用正確的姿勢去計算,接下來我們詳細的講講它是什么
有的小伙伴可能會認為內存讀取,就是一個簡單的字節(jié)數(shù)組擺放
上圖表示一個坑一個蘿卜的內存讀取方式。但實際上 CPU 并不會以一個一個字節(jié)去讀取和寫入內存。相反 CPU 讀取內存是 一塊一塊讀取 的,塊的大小可以為 2、4、6、8、16 字節(jié)等大小。塊大小我們稱其為 內存訪問粒度 。如下圖:
在樣例中,假設訪問粒度為 4。 CPU 是以每 4 個字節(jié)大小的訪問粒度去讀取和寫入內存的。這才是正確的姿勢
另外作為一個工程師,你也很有必要學習這塊知識點哦 :)
在上圖中,假設從 Index 1 開始讀取,將會出現(xiàn)很崩潰的問題。因為它的內存訪問邊界是不對齊的。因此 CPU 會做一些額外的處理工作。如下:
從上述流程可得出,不做 “內存對齊” 是一件有點 "麻煩" 的事。因為它會增加許多耗費時間的動作
而假設做了內存對齊,從 Index 0 開始讀取 4 個字節(jié),只需要讀取一次,也不需要額外的運算。這顯然高效很多,是標準的 空間換時間 做法
在不同平臺上的編譯器都有自己默認的 “對齊系數(shù)”,可通過預編譯命令 #pragma pack(n) 進行變更,n 就是代指 “對齊系數(shù)”。一般來講,我們常用的平臺的系數(shù)如下:
另外要注意,不同硬件平臺占用的大小和對齊值都可能是不一樣的。因此本文的值不是唯一的,調試的時候需按本機的實際情況考慮
輸出結果:
在 Go 中可以調用 unsafe.Alignof 來返回相應類型的對齊系數(shù)。通過觀察輸出結果,可得知基本都是 2^n ,最大也不會超過 8。這是因為我手提(64 位)編譯器默認對齊系數(shù)是 8,因此最大值不會超過這個數(shù)
在上小節(jié)中,提到了結構體中的成員變量要做字節(jié)對齊。那么想當然身為最終結果的結構體,也是需要做字節(jié)對齊的
接下來我們一起分析一下,“它” 到底經(jīng)歷了些什么,影響了 “預期” 結果
在每個成員變量進行對齊后,根據(jù)規(guī)則 2,整個結構體本身也要進行字節(jié)對齊,因為可發(fā)現(xiàn)它可能并不是 2^n ,不是偶數(shù)倍。顯然不符合對齊的規(guī)則
根據(jù)規(guī)則 2,可得出對齊值為 8。現(xiàn)在的偏移量為 25,不是 8 的整倍數(shù)。因此確定偏移量為 32。對結構體進行對齊
Part1 內存布局:axxx|bbbb|cxxx|xxxx|dddd|dddd|exxx|xxxx
通過本節(jié)的分析,可得知先前的 “推算” 為什么錯誤?
是因為實際內存管理并非 “一個蘿卜一個坑” 的思想。而是一塊一塊。通過空間換時間(效率)的思想來完成這塊讀取、寫入。另外也需要兼顧不同平臺的內存操作情況
在上一小節(jié),可得知根據(jù)成員變量的類型不同,其結構體的內存會產(chǎn)生對齊等動作。那假設字段順序不同,會不會有什么變化呢?我們一起來試試吧 :-)
輸出結果:
通過結果可以驚喜的發(fā)現(xiàn),只是 “簡單” 對成員變量的字段順序進行改變,就改變了結構體占用大小
接下來我們一起剖析一下 Part2 ,看看它的內部到底和上一位之間有什么區(qū)別,才導致了這樣的結果?
符合規(guī)則 2,不需要額外對齊
Part2 內存布局:ecax|bbbb|dddd|dddd
通過對比 Part1 和 Part2 的內存布局,你會發(fā)現(xiàn)兩者有很大的不同。如下:
仔細一看, Part1 存在許多 Padding。顯然它占據(jù)了不少空間,那么 Padding 是怎么出現(xiàn)的呢?
通過本文的介紹,可得知是由于不同類型導致需要進行字節(jié)對齊,以此保證內存的訪問邊界
那么也不難理解,為什么 調整結構體內成員變量的字段順序 就能達到縮小結構體占用大小的疑問了,是因為巧妙地減少了 Padding 的存在。讓它們更 “緊湊” 了。這一點對于加深 Go 的內存布局印象和大對象的優(yōu)化非常有幫
Go語言中沒有“類”的概念,也不支持“類”的繼承等面向對象的概念。Go語言中通過結構體的內嵌再配合接口比面向對象具有更高的擴展性和靈活性。
自定義類型
在Go語言中有一些基本的數(shù)據(jù)類型,如string、整型、浮點型、布爾等數(shù)據(jù)類型, Go語言中可以使用type關鍵字來定義自定義類型。
自定義類型是定義了一個全新的類型。我們可以基于內置的基本類型定義,也可以通過struct定義。例如:
通過Type關鍵字的定義,MyInt就是一種新的類型,它具有int的特性。
類型別名
類型別名是Go1.9版本添加的新功能。
類型別名規(guī)定:TypeAlias只是Type的別名,本質上TypeAlias與Type是同一個類型。就像一個孩子小時候有小名、乳名,上學后用學名,英語老師又會給他起英文名,但這些名字都指的是他本人。
type TypeAlias = Type
我們之前見過的rune和byte就是類型別名,他們的定義如下:
類型定義和類型別名的區(qū)別
類型別名與類型定義表面上看只有一個等號的差異,我們通過下面的這段代碼來理解它們之間的區(qū)別。
結果顯示a的類型是main.NewInt,表示main包下定義的NewInt類型。b的類型是int。MyInt類型只會在代碼中存在,編譯完成時并不會有MyInt類型。
Go語言中的基礎數(shù)據(jù)類型可以表示一些事物的基本屬性,但是當我們想表達一個事物的全部或部分屬性時,這時候再用單一的基本數(shù)據(jù)類型明顯就無法滿足需求了,Go語言提供了一種自定義數(shù)據(jù)類型,可以封裝多個基本數(shù)據(jù)類型,這種數(shù)據(jù)類型叫結構體,英文名稱struct。 也就是我們可以通過struct來定義自己的類型了。
Go語言中通過struct來實現(xiàn)面向對象。
結構體的定義
使用type和struct關鍵字來定義結構體,具體代碼格式如下:
其中:
舉個例子,我們定義一個Person(人)結構體,代碼如下:
同樣類型的字段也可以寫在一行,
這樣我們就擁有了一個person的自定義類型,它有name、city、age三個字段,分別表示姓名、城市和年齡。這樣我們使用這個person結構體就能夠很方便的在程序中表示和存儲人信息了。
語言內置的基礎數(shù)據(jù)類型是用來描述一個值的,而結構體是用來描述一組值的。比如一個人有名字、年齡和居住城市等,本質上是一種聚合型的數(shù)據(jù)類型
結構體實例化
只有當結構體實例化時,才會真正地分配內存。也就是必須實例化后才能使用結構體的字段。
基本實例化
舉個例子:
我們通過.來訪問結構體的字段(成員變量),例如p1.name和p1.age等。
匿名結構體
在定義一些臨時數(shù)據(jù)結構等場景下還可以使用匿名結構體。
創(chuàng)建指針類型結構體
我們還可以通過使用new關鍵字對結構體進行實例化,得到的是結構體的地址。 格式如下:
從打印的結果中我們可以看出p2是一個結構體指針。
需要注意的是在Go語言中支持對結構體指針直接使用.來訪問結構體的成員。
取結構體的地址實例化
使用對結構體進行取地址操作相當于對該結構體類型進行了一次new實例化操作。
p3.name = "七米"其實在底層是(*p3).name = "七米",這是Go語言幫我們實現(xiàn)的語法糖。
結構體初始化
沒有初始化的結構體,其成員變量都是對應其類型的零值。
使用鍵值對初始化
使用鍵值對對結構體進行初始化時,鍵對應結構體的字段,值對應該字段的初始值。
也可以對結構體指針進行鍵值對初始化,例如:
當某些字段沒有初始值的時候,該字段可以不寫。此時,沒有指定初始值的字段的值就是該字段類型的零值。
使用值的列表初始化
初始化結構體的時候可以簡寫,也就是初始化的時候不寫鍵,直接寫值:
使用這種格式初始化時,需要注意:
結構體內存布局
結構體占用一塊連續(xù)的內存。
輸出:
【進階知識點】關于Go語言中的內存對齊推薦閱讀:在 Go 中恰到好處的內存對齊
面試題
請問下面代碼的執(zhí)行結果是什么?
構造函數(shù)
Go語言的結構體沒有構造函數(shù),我們可以自己實現(xiàn)。 例如,下方的代碼就實現(xiàn)了一個person的構造函數(shù)。 因為struct是值類型,如果結構體比較復雜的話,值拷貝性能開銷會比較大,所以該構造函數(shù)返回的是結構體指針類型。
調用構造函數(shù)
方法和接收者
Go語言中的方法(Method)是一種作用于特定類型變量的函數(shù)。這種特定類型變量叫做接收者(Receiver)。接收者的概念就類似于其他語言中的this或者 self。
方法的定義格式如下:
其中,
舉個例子:
方法與函數(shù)的區(qū)別是,函數(shù)不屬于任何類型,方法屬于特定的類型。
指針類型的接收者
指針類型的接收者由一個結構體的指針組成,由于指針的特性,調用方法時修改接收者指針的任意成員變量,在方法結束后,修改都是有效的。這種方式就十分接近于其他語言中面向對象中的this或者self。 例如我們?yōu)镻erson添加一個SetAge方法,來修改實例變量的年齡。
調用該方法:
值類型的接收者
當方法作用于值類型接收者時,Go語言會在代碼運行時將接收者的值復制一份。在值類型接收者的方法中可以獲取接收者的成員值,但修改操作只是針對副本,無法修改接收者變量本身。
什么時候應該使用指針類型接收者
任意類型添加方法
在Go語言中,接收者的類型可以是任何類型,不僅僅是結構體,任何類型都可以擁有方法。 舉個例子,我們基于內置的int類型使用type關鍵字可以定義新的自定義類型,然后為我們的自定義類型添加方法。
注意事項: 非本地類型不能定義方法,也就是說我們不能給別的包的類型定義方法。
結構體的匿名字段
匿名字段默認采用類型名作為字段名,結構體要求字段名稱必須唯一,因此一個結構體中同種類型的匿名字段只能有一個。
嵌套結構體
一個結構體中可以嵌套包含另一個結構體或結構體指針。
嵌套匿名結構體
當訪問結構體成員時會先在結構體中查找該字段,找不到再去匿名結構體中查找。
嵌套結構體的字段名沖突
嵌套結構體內部可能存在相同的字段名。這個時候為了避免歧義需要指定具體的內嵌結構體的字段。
結構體的“繼承”
Go語言中使用結構體也可以實現(xiàn)其他編程語言中面向對象的繼承。
結構體字段的可見性
結構體中字段大寫開頭表示可公開訪問,小寫表示私有(僅在定義當前結構體的包中可訪問)。
結構體與JSON序列化
JSON(JavaScript Object Notation) 是一種輕量級的數(shù)據(jù)交換格式。易于人閱讀和編寫。同時也易于機器解析和生成。JSON鍵值對是用來保存JS對象的一種方式,鍵/值對組合中的鍵名寫在前面并用雙引號""包裹,使用冒號:分隔,然后緊接著值;多個鍵值之間使用英文,分隔。
結構體標簽(Tag)
Tag是結構體的元信息,可以在運行的時候通過反射的機制讀取出來。 Tag在結構體字段的后方定義,由一對反引號包裹起來,具體的格式如下:
`key1:"value1" key2:"value2"`
結構體標簽由一個或多個鍵值對組成。鍵與值使用冒號分隔,值用雙引號括起來。鍵值對之間使用一個空格分隔。 注意事項: 為結構體編寫Tag時,必須嚴格遵守鍵值對的規(guī)則。結構體標簽的解析代碼的容錯能力很差,一旦格式寫錯,編譯和運行時都不會提示任何錯誤,通過反射也無法正確取值。例如不要在key和value之間添加空格。
例如我們?yōu)镾tudent結構體的每個字段定義json序列化時使用的Tag:
分享名稱:go語言內存對齊有什么用 go語言內存對齊有什么用處
地址分享:http://chinadenli.net/article12/dojidgc.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供域名注冊、外貿建站、標簽優(yōu)化、網(wǎng)站排名、自適應網(wǎng)站、建站公司
聲明:本網(wǎng)站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經(jīng)允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)