golang slice和string重用

相比于 c/c++芭商,golang 的一個(gè)很大的改進(jìn)就是引入了 gc 機(jī)制,不再需要用戶自己管理內(nèi)存搀缠,大大減少了程序由于內(nèi)存泄露而引入的 bug铛楣,但是同時(shí) gc 也帶來了額外的性能開銷,有時(shí)甚至?xí)驗(yàn)槭褂貌划?dāng)胡嘿,導(dǎo)致 gc 成為性能瓶頸蛉艾,所以 golang 程序設(shè)計(jì)的時(shí)候,應(yīng)特別注意對(duì)象的重用,以減少 gc 的壓力勿侯。而 slice 和 string 是 golang 的基本類型拓瞪,了解這些基本類型的內(nèi)部機(jī)制,有助于我們更好地重用這些對(duì)象

slice 和 string 內(nèi)部結(jié)構(gòu)

slice 和 string 的內(nèi)部結(jié)構(gòu)可以在 $GOROOT/src/reflect/value.go 里面找到

type StringHeader struct {
    Data uintptr
    Len  int
}

type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

可以看到一個(gè) string 包含一個(gè)數(shù)據(jù)指針和一個(gè)長度助琐,長度是不可變的

slice 包含一個(gè)數(shù)據(jù)指針祭埂、一個(gè)長度和一個(gè)容量,當(dāng)容量不夠時(shí)會(huì)重新申請(qǐng)新的內(nèi)存兵钮,Data 指針將指向新的地址蛆橡,原來的地址空間將被釋放

從這些結(jié)構(gòu)就可以看出,string 和 slice 的賦值掘譬,包括當(dāng)做參數(shù)傳遞泰演,和自定義的結(jié)構(gòu)體一樣,都僅僅是 Data 指針的淺拷貝

slice 重用

append 操作

si1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
si2 := si1
si2 = append(si2, 0)
Convey("重新分配內(nèi)存", func() {
    header1 := (*reflect.SliceHeader)(unsafe.Pointer(&si1))
    header2 := (*reflect.SliceHeader)(unsafe.Pointer(&si2))
    fmt.Println(header1.Data)
    fmt.Println(header2.Data)
    So(header1.Data, ShouldNotEqual, header2.Data)
})

si1 和 si2 開始都指向同一個(gè)數(shù)組葱轩,當(dāng)對(duì) si2 執(zhí)行 append 操作時(shí)睦焕,由于原來的 Cap 值不夠了,需要重新申請(qǐng)新的空間靴拱,因此 Data 值發(fā)生了變化垃喊,在 $GOROOT/src/reflect/value.go 這個(gè)文件里面還有關(guān)于新的 cap 值的策略,在 grow 這個(gè)函數(shù)里面袜炕,當(dāng) cap 小于 1024 的時(shí)候本谜,是成倍的增長,超過的時(shí)候偎窘,每次增長 25%乌助,而這種內(nèi)存增長不僅僅數(shù)據(jù)拷貝(從舊的地址拷貝到新的地址)需要消耗額外的性能,舊地址內(nèi)存的釋放對(duì) gc 也會(huì)造成額外的負(fù)擔(dān)评架,所以如果能夠知道數(shù)據(jù)的長度的情況下眷茁,盡量使用 make([]int, len, cap) 預(yù)分配內(nèi)存,不知道長度的情況下纵诞,可以考慮下面的內(nèi)存重用的方法

內(nèi)存重用

si1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
si2 := si1[:7]
Convey("不重新分配內(nèi)存", func() {
    header1 := (*reflect.SliceHeader)(unsafe.Pointer(&si1))
    header2 := (*reflect.SliceHeader)(unsafe.Pointer(&si2))
    fmt.Println(header1.Data)
    fmt.Println(header2.Data)
    So(header1.Data, ShouldEqual, header2.Data)
})

Convey("往切片里面 append 一個(gè)值", func() {
    si2 = append(si2, 10)
    Convey("改變了原 slice 的值", func() {
        header1 := (*reflect.SliceHeader)(unsafe.Pointer(&si1))
        header2 := (*reflect.SliceHeader)(unsafe.Pointer(&si2))
        fmt.Println(header1.Data)
        fmt.Println(header2.Data)
        So(header1.Data, ShouldEqual, header2.Data)
        So(si1[7], ShouldEqual, 10)
    })
})

si2 是 si1 的一個(gè)切片上祈,從第一段代碼可以看到切片并不重新分配內(nèi)存,si2 和 si1 的 Data 指針指向同一片地址浙芙,而第二段代碼可以看出登刺,當(dāng)我們往 si2 里面 append 一個(gè)新的值的時(shí)候,我們發(fā)現(xiàn)仍然沒有內(nèi)存分配嗡呼,而且這個(gè)操作使得 si1 的值也發(fā)生了改變纸俭,因?yàn)閮烧弑揪褪侵赶蛲黄?Data 區(qū)域,利用這個(gè)特性南窗,我們只需要讓 si1 = si1[:0] 就可以不斷地清空 si1 的內(nèi)容揍很,實(shí)現(xiàn)內(nèi)存的復(fù)用了

PS: 你可以使用 copy(si2, si1) 實(shí)現(xiàn)深拷貝

string

Convey("字符串常量", func() {
    str1 := "hello world"
    str2 := "hello world"
    Convey("地址相同", func() {
        header1 := (*reflect.StringHeader)(unsafe.Pointer(&str1))
        header2 := (*reflect.StringHeader)(unsafe.Pointer(&str2))
        fmt.Println(header1.Data)
        fmt.Println(header2.Data)
        So(header1.Data, ShouldEqual, header2.Data)
    })
})

這個(gè)例子比較簡單郎楼,字符串常量使用的是同一片地址區(qū)域

Convey("相同字符串的不同子串", func() {
    str1 := "hello world"[:6]
    str2 := "hello world"[:5]
    Convey("地址相同", func() {
        header1 := (*reflect.StringHeader)(unsafe.Pointer(&str1))
        header2 := (*reflect.StringHeader)(unsafe.Pointer(&str2))
        fmt.Println(header1.Data, str1)
        fmt.Println(header2.Data, str2)
        So(str1, ShouldNotEqual, str2)
        So(header1.Data, ShouldEqual, header2.Data)
    })
})

相同字符串的不同子串,不會(huì)額外申請(qǐng)新的內(nèi)存窒悔,但是要注意的是這里的相同字符串呜袁,指的是 str1.Data == str2.Data && str1.Len == str2.Len,而不是 str1 == str2简珠,下面這個(gè)例子可以說明 str1 == str2 但是其 Data 并不相同

Convey("不同字符串的相同子串", func() {
    str1 := "hello world"[:5]
    str2 := "hello golang"[:5]
    Convey("地址不同", func() {
        header1 := (*reflect.StringHeader)(unsafe.Pointer(&str1))
        header2 := (*reflect.StringHeader)(unsafe.Pointer(&str2))
        fmt.Println(header1.Data, str1)
        fmt.Println(header2.Data, str2)
        So(str1, ShouldEqual, str2)
        So(header1.Data, ShouldNotEqual, header2.Data)
    })
})

實(shí)際上對(duì)于字符串阶界,你只需要記住一點(diǎn),字符串是不可變的聋庵,任何字符串的操作都不會(huì)申請(qǐng)額外的內(nèi)存(對(duì)于僅內(nèi)部數(shù)據(jù)指針而言)膘融,我曾自作聰明地設(shè)計(jì)了一個(gè) cache 去存儲(chǔ)字符串,以減少重復(fù)字符串所占用的空間祭玉,事實(shí)上氧映,除非這個(gè)字符串本身就是由 []byte 創(chuàng)建而來,否則攘宙,這個(gè)字符串本身就是另一個(gè)字符串的子串(比如通過 strings.Split 獲得的字符串)屯耸,本來就不會(huì)申請(qǐng)額外的空間,這么做簡直就是多此一舉

參考鏈接

原文作者:hatlonely
文章出處:http://www.hatlonely.com/2018/03/17/golang-slice-和-string-重用/

文章不定期更新蹭劈,小編微信:grey0805,歡迎交流

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末线召,一起剝皮案震驚了整個(gè)濱河市铺韧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌缓淹,老刑警劉巖哈打,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異讯壶,居然都是意外死亡料仗,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門伏蚊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來立轧,“玉大人,你說我怎么就攤上這事躏吊》崭模” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵比伏,是天一觀的道長胜卤。 經(jīng)常有香客問我,道長赁项,這世上最難降的妖魔是什么葛躏? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任澈段,我火速辦了婚禮,結(jié)果婚禮上舰攒,老公的妹妹穿的比我還像新娘败富。我一直安慰自己,他們只是感情好芒率,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布囤耳。 她就那樣靜靜地躺著,像睡著了一般偶芍。 火紅的嫁衣襯著肌膚如雪充择。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天匪蟀,我揣著相機(jī)與錄音椎麦,去河邊找鬼。 笑死材彪,一個(gè)胖子當(dāng)著我的面吹牛观挎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播段化,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼嘁捷,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了显熏?” 一聲冷哼從身側(cè)響起雄嚣,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎喘蟆,沒想到半個(gè)月后缓升,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蕴轨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年港谊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片橙弱。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡歧寺,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出膘螟,到底是詐尸還是另有隱情成福,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布荆残,位于F島的核電站奴艾,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏内斯。R本人自食惡果不足惜蕴潦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一像啼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧潭苞,春花似錦忽冻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蝗碎,卻和暖如春湖笨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蹦骑。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工慈省, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人眠菇。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓边败,卻偏偏與公主長得像,于是被迫代替她去往敵國和親捎废。 傳聞我的和親對(duì)象是個(gè)殘疾皇子笑窜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348