在組內(nèi)學(xué)習(xí)go語言規(guī)范時,學(xué)習(xí)到了一個很有意思并且能減少內(nèi)存分配提高性能的東西吩翻,先通過一個簡單的問題向大家展示一下~
type Example struct {
Bool bool
Int int32
Long int64
}
Example 結(jié)構(gòu)體中 bool 占1個字節(jié),int32占4個字節(jié),int64占8個字節(jié)洪乍,實(shí)際上這個結(jié)構(gòu)體一個對象總共占用了16個字節(jié),可以通過 unsafe.Sizeof()函數(shù)來查看所占內(nèi)存大小夜焦。
type Example struct {
Bool bool
Long int64
Int int32
}
那我在調(diào)換一下int 和 long 的位置壳澳,這個結(jié)構(gòu)體的一個對象就占用24個字節(jié)了,是不是很有意思茫经,接下來就引出了go 語言內(nèi)存對齊的話題
內(nèi)存對齊
為了最大限度地減少內(nèi)存碎片整理巷波,go在分配內(nèi)存時都會將內(nèi)存邊界對齊。要確定 Go 在體系結(jié)構(gòu)上所用的對齊邊界卸伞,你可以運(yùn)行 unsafe.Alignof 函數(shù)抹镊。Go 在 64 位系統(tǒng)的對齊邊界是 8 個字節(jié)。因此在 Go 確定我們結(jié)構(gòu)體的內(nèi)存分配時荤傲,它將填充字節(jié)以確保最終占用的內(nèi)存是 8 的倍數(shù)垮耳。unsafe.Offsetof取結(jié)構(gòu)體字段內(nèi)存的offset值。下面通過Example 這個結(jié)構(gòu)體來具體展示下
type Example struct {
Bool bool
Int int32
Long int64
}
example := Example{}
alignmentBoundary := unsafe.Alignof(example)
fmt.Printf("Example Alignof Size : %d\n", alignmentBoundary)
sizeBool := unsafe.Sizeof(example.Bool)
offsetBool := unsafe.Offsetof(example.Bool)
fmt.Printf("BoolValue = Size: %d Offset: %d Addr: %v\n", sizeBool, offsetBool, &example.Bool)
sizeInt := unsafe.Sizeof(example.Int)
offsetInt := unsafe.Offsetof(example.Int)
fmt.Printf("IntValue = Size: %d Offset: %d Addr: %v\n", sizeInt, offsetInt, &example.Int)
sizeLong := unsafe.Sizeof(example.Long)
offsetLong := unsafe.Offsetof(example.Long)
fmt.Printf("LongValue = Size: %d Offset: %d Addr: %v\n", sizeLong, offsetLong, &example.Long)
fmt.Printf("Example SizeOf : %d\n",unsafe.Sizeof(example))
以上代碼的輸出結(jié)果為
Example Alignof Size : 8
BoolValue = Size: 1 Offset: 0 Addr: 0xc0000a4030
IntValue = Size: 4 Offset: 4 Addr: 0xc0000a4034
LongValue = Size: 8 Offset: 8 Addr: 0xc0000a4038
Example SizeOf : 16
bool 和 int 占用了8個字節(jié)弃酌,在8個字節(jié)中 offset 1-4 之間go填充了內(nèi)存氨菇,long正好占用了8個字節(jié)。通過unsafe.Sizeof 看到結(jié)構(gòu)體占了16個字節(jié)妓湘,再將結(jié)構(gòu)體中的int 和 long 調(diào)一下位置查蓉,會有意想不到的效果。
type Example struct {
Bool bool
Long int64
Int int32
}
example := Example{}
alignmentBoundary := unsafe.Alignof(example)
fmt.Printf("Example Alignof Size : %d\n", alignmentBoundary)
sizeBool := unsafe.Sizeof(example.Bool)
offsetBool := unsafe.Offsetof(example.Bool)
fmt.Printf("BoolValue = Size: %d Offset: %d Addr: %v\n", sizeBool, offsetBool, &example.Bool)
sizeLong := unsafe.Sizeof(example.Long)
offsetLong := unsafe.Offsetof(example.Long)
fmt.Printf("LongValue = Size: %d Offset: %d Addr: %v\n", sizeLong, offsetLong, &example.Long)
sizeInt := unsafe.Sizeof(example.Int)
offsetInt := unsafe.Offsetof(example.Int)
fmt.Printf("IntValue = Size: %d Offset: %d Addr: %v\n", sizeInt, offsetInt, &example.Int)
fmt.Printf("Example SizeOf : %d\n",unsafe.Sizeof(example))
以上輸出結(jié)果為
Example Alignof Size : 8
BoolValue = Size: 1 Offset: 0 Addr: 0xc00001c0e0
LongValue = Size: 8 Offset: 8 Addr: 0xc00001c0e8
IntValue = Size: 4 Offset: 16 Addr: 0xc00001c0f0
Example SizeOf : 24
說明:由于bool 占用1個字節(jié)榜贴,long占用8個字節(jié)豌研,第一個對齊邊界還剩4字節(jié)放不下long類型的8個字節(jié)妹田,因此會重新開辟一個邊界8字節(jié),long正好占滿鹃共,剩下的int 在開辟一個8字節(jié)鬼佣,剩余的go會把內(nèi)存填充。所以Example 占用了24個字節(jié)霜浴。因此在定義結(jié)構(gòu)體時晶衷,還是要考慮字段順序問題,這樣能減少不必要的內(nèi)存分配阴孟,減少gc晌纫,在流量大的情況下能提高系統(tǒng)性能
unsafe 內(nèi)存操作
借助unsafe.Pointer 我們可以拿到結(jié)構(gòu)體中字段的指針,然后修改它的值永丝,舉例如下
e := &Example{
Bool: true,
Int: 1,
Long: 1,
}
fmt.Printf("Example before value %+v \n",e)
pointer := unsafe.Pointer(e)
// bool 設(shè)置值
b := (*bool)(pointer)
*b = false
// int 設(shè)置值
i := (*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(e)) + unsafe.Offsetof(e.Int)))
*i = 100
l := (*int64)(unsafe.Pointer(uintptr(unsafe.Pointer(e)) + 8))
*l = 1000
fmt.Printf("Example after value %+v \n",e)
輸出結(jié)果如下
Example before value &{Bool:true Int:1 Long:1}
Example after value &{Bool:false Int:100 Long:1000}
當(dāng)結(jié)構(gòu)體的某些字段是小寫锹漱,并且被別的包引用時,只要能拿到這個結(jié)構(gòu)體的實(shí)例慕嚷,算好字段的offset就可以通過Pointer修改結(jié)構(gòu)體字段的值哥牍,感覺還是很有意思的~