Golang對struct字段重新排序優(yōu)化數(shù)據(jù)結(jié)構(gòu)性能實踐
通過對 struct 字段重新排序
僅僅通過對 struct 字段重新排序,優(yōu)化內(nèi)存對齊方式,就可以獲得明顯的內(nèi)存和執(zhí)行效率提升。
如果你有 Golang 開發(fā)經(jīng)驗,一定定義過 struct 類型。
但可能你不知道,通過簡單的重新排序 struct 字段,可以極大提高 Go 程序的速度和內(nèi)存使用效率!
是不是難以置信?我們一起來看一下吧!
簡單 Demo
type BadStruct struct { age uint8 passportNum uint64 siblings uint16 } type GoodStruct struct { age uint8 siblings uint16 passportNum uint64 }
在上面的代碼片段中,我們創(chuàng)建了兩個具有相同字段的結(jié)構(gòu)體。然后編寫一個簡單程序分別輸出其內(nèi)存使用情況。
// Output Bad struct is 24 bytes long Good struct is 16 bytes long
如你所見,它們在內(nèi)存使用方面并不一樣。
是什么原因?qū)е聝蓚€完全相似的 struct 消耗的內(nèi)存不同?
答案在于數(shù)據(jù)在計算機內(nèi)存中的排列方式。
簡而言之,數(shù)據(jù)結(jié)構(gòu)對齊。
數(shù)據(jù)結(jié)構(gòu)對齊
CPU 以字(word)為單位讀取數(shù)據(jù),而不是字節(jié)(byte)。
64 位系統(tǒng)中,一個 word 是 8 個字節(jié),而 32 位系統(tǒng)中,一個 word 是 4 個字節(jié)。
簡而言之,CPU 以其字長的倍數(shù)讀取內(nèi)存地址。
想象一下,在 64 位系統(tǒng)中,為了獲取變量passportNum
,CPU 需要兩個周期來訪問數(shù)據(jù)。
第一個周期將獲取內(nèi)存的 0 到 7 字節(jié),下一個周期獲取其余內(nèi)存字節(jié)。
把它想象成一個筆記本,每頁只能存儲一個字大小的數(shù)據(jù)(在本例中為 8 字節(jié))。如果passportNum
分散在兩個頁,則需要兩次讀取才能檢索到完整的數(shù)據(jù)。
非常低效。
因此需要數(shù)據(jù)結(jié)構(gòu)對齊,讓計算機將數(shù)據(jù)存儲在等于數(shù)據(jù)大小倍數(shù)的地址上。
例如,2 字節(jié)數(shù)據(jù)可以存儲在內(nèi)存 0、2 或 4 中,而 4 字節(jié)數(shù)據(jù)可以存儲在內(nèi)存 0、4 或 8 中。
通過簡單的對齊數(shù)據(jù),計算機確??梢栽谝粋€ CPU 周期內(nèi)檢索到變量passportNum
。
數(shù)據(jù)結(jié)構(gòu)填充
填充是實現(xiàn)數(shù)據(jù)對齊的關(guān)鍵。
計算機通過在數(shù)據(jù)結(jié)構(gòu)之間填充額外的字節(jié),從而對齊字段。
這就是額外內(nèi)存的來源!
我們來回顧一下BadStruct
和GoodStruct
。
GoodStruct
消耗更少的內(nèi)存,僅僅因為與BadStruct
相比,其 struct 字段順序更合理。
由于填充,兩個 13 字節(jié)的數(shù)據(jù)結(jié)構(gòu)分別變成了 16 字節(jié)和 24 字節(jié)。
因此,可以僅僅通過對 struct 字段重新排序來節(jié)省額外的內(nèi)存!
這種優(yōu)化為什么重要?
問題來了,你為什么要關(guān)心這個?
兩個方面,速度和內(nèi)存使用。
我們做一個簡單的基準測試來證明!
func traverseGoodStruct() uint16 { var arbitraryNum uint16 for _, goodStruct := range GoodStructArr { arbitraryNum += goodStruct.siblings } return arbitraryNum } func traverseBadStruct() uint16 { var arbitraryNum uint16 for _, badStruct := range BadStructArr { arbitraryNum += badStruct.siblings } return arbitraryNum } func BenchmarkTraverseGoodStruct(b *testing.B) { for n := 0; n < b.N; n++ { traverseGoodStruct() } } func BenchmarkTraverseBadStruct(b *testing.B) { for n := 0; n < b.N; n++ { traverseBadStruct() } }
對GoodStruct
和BadStruct
進行基準測試的方法是循環(huán)遍歷數(shù)組,并將 struct 字段累加到變量中。
從結(jié)果中可以看出,遍歷GoodStruct
確實比BadStruct
花費時間更少。
對 struct 字段重排序可以優(yōu)化應用程序的內(nèi)存使用和速度。
想象一下,維護一個具有大量結(jié)構(gòu)體的大型應用程序,改變將會更為明顯。
結(jié)語
好了,全文到此為止,我們以一個簡單的行動呼吁來結(jié)束:一定要對 struct 結(jié)構(gòu)字段進行重排序!
以上就是Golang對struct字段重新排序優(yōu)化數(shù)據(jù)結(jié)構(gòu)性能實踐的詳細內(nèi)容,更多關(guān)于Golang struct重新排序的資料請關(guān)注腳本之家其它相關(guān)文章!
- 淺析Go語言中的map數(shù)據(jù)結(jié)構(gòu)是如何實現(xiàn)的
- Go數(shù)據(jù)結(jié)構(gòu)之HeapMap實現(xiàn)指定Key刪除堆
- Golang實現(xiàn)數(shù)據(jù)結(jié)構(gòu)Stack(堆棧)的示例詳解
- Golang迭代如何在Go中循環(huán)數(shù)據(jù)結(jié)構(gòu)使用詳解
- 詳解如何在Go語言中循環(huán)數(shù)據(jù)結(jié)構(gòu)
- Go語言數(shù)據(jù)結(jié)構(gòu)之二叉樹可視化詳解
- Go 數(shù)據(jù)結(jié)構(gòu)之堆排序示例詳解
- Go實現(xiàn)List、Set、Stack、Deque等數(shù)據(jù)結(jié)構(gòu)的操作方法
相關(guān)文章
Go+Vue開發(fā)一個線上外賣應用的流程(用戶名密碼和圖形驗證碼)
這篇文章主要介紹了Go+Vue開發(fā)一個線上外賣應用(用戶名密碼和圖形驗證碼),本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-11-11