Go語言實(shí)戰(zhàn)之切片內(nèi)存優(yōu)化
切片為什么要做內(nèi)存優(yōu)化
Go 語言的切片是一個(gè)動(dòng)態(tài)的數(shù)據(jù)結(jié)構(gòu),可以方便地對(duì)其進(jìn)行擴(kuò)容和縮容操作。由于切片的底層實(shí)現(xiàn)是通過數(shù)組來實(shí)現(xiàn)的,因此在使用切片時(shí),需要注意內(nèi)存分配和釋放的開銷。這也是為什么需要對(duì)切片的內(nèi)存使用進(jìn)行優(yōu)化的原因。
內(nèi)存分配和釋放是非常耗時(shí)的操作,因此頻繁地對(duì)切片進(jìn)行重新分配和釋放會(huì)影響程序的性能和效率。當(dāng)程序中的數(shù)據(jù)量增加時(shí),內(nèi)存分配和釋放的開銷也會(huì)增加,這會(huì)導(dǎo)致程序變得更加緩慢。
因此,在使用切片時(shí),需要注意內(nèi)存使用的優(yōu)化,盡可能地避免頻繁地進(jìn)行內(nèi)存分配和釋放操作。優(yōu)化內(nèi)存使用可以減少程序的運(yùn)行時(shí)間和內(nèi)存占用,提高程序的性能和效率。
切片優(yōu)化內(nèi)存的技巧
Go 語言中的切片是一個(gè)非常方便的數(shù)據(jù)結(jié)構(gòu),它可以動(dòng)態(tài)地增加或縮小其長度。在處理大量數(shù)據(jù)的情況下,對(duì)切片的內(nèi)存使用進(jìn)行優(yōu)化是非常重要的。下面是一些優(yōu)化切片內(nèi)存使用的技巧:
- 預(yù)分配切片的容量 在創(chuàng)建切片時(shí),如果能夠預(yù)先知道其容量,最好設(shè)置好預(yù)期的容量。這樣可以避免內(nèi)存重新分配的開銷。
- 重用底層數(shù)組 盡可能地重用底層數(shù)組可以減少內(nèi)存分配和釋放的開銷??梢允褂们衅那衅僮骱?copy 函數(shù)來復(fù)制數(shù)據(jù),避免創(chuàng)建新的切片。
- 使用 append 函數(shù)時(shí)預(yù)分配容量 如果在使用 append 函數(shù)時(shí)預(yù)先分配足夠的容量,可以避免內(nèi)存重新分配的開銷。盡可能地避免在循環(huán)中多次使用 append 函數(shù),這將導(dǎo)致多次內(nèi)存重新分配。
- 使用 sync.Pool 減少內(nèi)存分配和釋放的開銷 sync.Pool 是 Go 語言中用于池化對(duì)象的包。通過使用 sync.Pool,可以重復(fù)使用之前分配的對(duì)象,避免頻繁的內(nèi)存分配和釋放操作。
總之,在使用切片時(shí),需要注意內(nèi)存分配和釋放的開銷,并盡可能地優(yōu)化內(nèi)存使用,以提高程序的性能和效率。
實(shí)戰(zhàn)案例
1.通過重用底層數(shù)組來避免內(nèi)存分配和釋放的開銷
package?main import?"fmt" func?main()?{ ????var?s1?[]int ????var?s2?[]int ????for?i?:=?0;?i?<?10000000;?i++?{ ????????s1?=?append(s1,?i) ????????s2?=?append(s2,?i*2) ????} ????fmt.Printf("s1:?%d,?s2:?%d\n",?len(s1),?len(s2)) ????s1?=?s1[:0] ????s2?=?s2[:0] ????for?i?:=?0;?i?<?10000000;?i++?{ ????????s1?=?append(s1,?i) ????????s2?=?append(s2,?i*2) ????} ????fmt.Printf("s1:?%d,?s2:?%d\n",?len(s1),?len(s2)) ????s1?=?s1[:0] ????s2?=?s2[:0] ????for?i?:=?0;?i?<?10000000;?i++?{ ????????if?i?<?len(s1)?{ ????????????s1[i]?=?i ????????}?else?{ ????????????s1?=?append(s1,?i) ????????} ????????if?i?<?len(s2)?{ ????????????s2[i]?=?i?*?2 ????????}?else?{ ????????????s2?=?append(s2,?i*2) ????????} ????} ????fmt.Printf("s1:?%d,?s2:?%d\n",?len(s1),?len(s2)) }
這個(gè)程序中,首先通過 append 函數(shù)向兩個(gè)切片 s1 和 s2 中添加了 10000000 個(gè)元素。然后,通過將切片設(shè)置為切片的零長度來重用底層數(shù)組,避免頻繁的內(nèi)存分配和釋放操作。最后,通過直接訪問切片中的元素來避免創(chuàng)建新的切片。
運(yùn)行該程序,可以看到輸出結(jié)果:
[root@devhost temp-test]# go run test-temp.go
s1: 10000000, s2: 10000000
s1: 10000000, s2: 10000000
s1: 10000000, s2: 10000000
[root@devhost temp-test]#
可以看到,在重用底層數(shù)組之后,程序的運(yùn)行時(shí)間沒有顯著變化,并且內(nèi)存使用也更加高效。
2.使用 sync.Pool 減少內(nèi)存分配和釋放的開銷案例 假設(shè)我們需要對(duì)一個(gè)較大的二維數(shù)組進(jìn)行遍歷,并對(duì)每個(gè)元素進(jìn)行處理。由于該數(shù)組的大小較大,為了減少內(nèi)存分配和釋放的開銷,我們可以使用 sync.Pool 來緩存一部分已經(jīng)分配的內(nèi)存。
package?main import?( ?"fmt" ?"math/rand" ?"sync" ?"time" ) const?( ?rows?=?10000 ?cols?=?10000 ) func?main()?{ ?//?生成二維數(shù)組 ?rand.Seed(time.Now().UnixNano()) ?arr?:=?make([][]int,?rows) ?for?i?:=?range?arr?{ ??arr[i]?=?make([]int,?cols) ??for?j?:=?range?arr[i]?{ ???arr[i][j]?=?rand.Intn(1000) ??} ?} ?//?使用?sync.Pool?緩存一部分內(nèi)存 ?pool?:=?sync.Pool{ ??New:?func()?interface{}?{ ???return?make([]int,?cols) ??}, ?} ?//?遍歷二維數(shù)組并對(duì)每個(gè)元素進(jìn)行處理 ?for?i?:=?range?arr?{ ??row?:=?pool.Get().([]int) ??copy(row,?arr[i]) ??go?func(row?[]int)?{ ???for?j?:=?range?row?{ ????row[j]?=?process(row[j]) ???} ???pool.Put(row) ??}(row) ?} ?fmt.Println("All?elements?are?processed!") } //?對(duì)元素進(jìn)行處理的函數(shù) func?process(x?int)?int?{ ?time.Sleep(time.Duration(x)?*?time.Millisecond) ?return?x?*?2 }
運(yùn)行該程序,可以看到輸出結(jié)果:
[root@devhost temp-test]# go run test-temp.go
All elements are processed!
上述代碼中,我們使用 sync.Pool 緩存了一部分大小為 cols 的整型數(shù)組,并在遍歷二維數(shù)組時(shí)使用 Get() 方法從緩存中獲取一個(gè)數(shù)組進(jìn)行處理。由于 Get() 方法返回的是一個(gè) interface{} 類型的對(duì)象,需要使用類型斷言轉(zhuǎn)換為正確的類型。在處理完一個(gè)數(shù)組后,我們將其歸還到緩存池中,以便下一次使用時(shí)能夠直接獲取已經(jīng)分配的內(nèi)存,而不需要重新進(jìn)行分配。
在處理元素時(shí),我們還使用了 go 關(guān)鍵字開啟了一個(gè)新的協(xié)程來執(zhí)行處理操作,以充分利用 CPU 的多核能力。在處理完成后,我們將該數(shù)組歸還到緩存池中,以便下一次使用。
通過使用 sync.Pool 緩存一部分已經(jīng)分配的內(nèi)存,可以避免頻繁地進(jìn)行內(nèi)存分配和釋放,從而提高程序的性能和效率。
3.使用 append 函數(shù)時(shí)預(yù)分配容量的案例 假設(shè)我們需要向一個(gè)空的切片中添加 1000000 個(gè)元素,并對(duì)每個(gè)元素進(jìn)行處理。由于 append 函數(shù)會(huì)在需要時(shí)自動(dòng)擴(kuò)展切片的容量,頻繁的擴(kuò)容操作會(huì)帶來較大的性能開銷,因此我們可以在使用 append 函數(shù)前預(yù)分配切片的容量,以減少擴(kuò)容操作的次數(shù)。
package?main import?( ?"fmt" ?"math/rand" ?"time" ) const?( ?n?=?1000000 ) func?main()?{ ?//?預(yù)分配切片的容量 ?data?:=?make([]int,?0,?n) ?//?向切片中添加元素并處理 ?rand.Seed(time.Now().UnixNano()) ?for?i?:=?0;?i?<?n;?i++?{ ??data?=?append(data,?rand.Intn(1000)) ?} ?for?i?:=?range?data?{ ??data[i]?=?process(data[i]) ?} ?fmt.Println("All?elements?are?processed!") } //?對(duì)元素進(jìn)行處理的函數(shù) func?process(x?int)?int?{ ?time.Sleep(time.Duration(x)?*?time.Millisecond) ?return?x?*?2 }
在上述代碼中,我們使用 make([]int, 0, n) 預(yù)分配了一個(gè)切片,其長度為 0,容量為 n,即預(yù)留了 n 個(gè)元素的存儲(chǔ)空間。在向切片中添加元素時(shí),由于容量已經(jīng)預(yù)分配好了,append 函數(shù)不會(huì)進(jìn)行擴(kuò)容操作,從而減少了性能開銷。
需要注意的是,如果預(yù)分配的容量過小,仍然會(huì)進(jìn)行擴(kuò)容操作,從而導(dǎo)致性能下降。因此,預(yù)分配的容量應(yīng)根據(jù)實(shí)際情況進(jìn)行調(diào)整。
4.使用預(yù)分配切片容量的案例 假設(shè)我們有一個(gè)函數(shù) readData(),可以讀取一個(gè)很大的數(shù)據(jù)文件,并將數(shù)據(jù)逐行解析為字符串?dāng)?shù)組,我們需要將這些字符串進(jìn)行進(jìn)一步處理。由于我們無法事先確定數(shù)據(jù)文件的大小,因此我們需要?jiǎng)討B(tài)地將讀取到的字符串添加到切片中。
為了避免 append 函數(shù)頻繁地進(jìn)行擴(kuò)容操作,我們可以在讀取數(shù)據(jù)前,預(yù)估數(shù)據(jù)文件的大小,并預(yù)分配切片的容量。
package?main import?( ?"fmt" ?"os" ?"bufio" ?"strings" ) func?main()?{ ?//?預(yù)估數(shù)據(jù)文件的大小 ?const?estSize?=?1000000 ?//?預(yù)分配切片的容量 ?data?:=?make([]string,?0,?estSize) ?//?讀取數(shù)據(jù)文件 ?file,?err?:=?os.Open("data.txt") ?if?err?!=?nil?{ ??panic(err) ?} ?defer?file.Close() ?scanner?:=?bufio.NewScanner(file) ?for?scanner.Scan()?{ ??line?:=?scanner.Text() ??//?將讀取到的字符串添加到切片中 ??data?=?append(data,?line) ?} ?if?err?:=?scanner.Err();?err?!=?nil?{ ??panic(err) ?} ?//?對(duì)字符串進(jìn)行處理 ?for?i,?str?:=?range?data?{ ??data[i]?=?process(str) ?} ?fmt.Println("All?strings?are?processed!") } //?對(duì)字符串進(jìn)行處理的函數(shù) func?process(s?string)?string?{ ?return?strings.ToUpper(s) }
在上述代碼中,我們使用 make([]string, 0, estSize) 預(yù)分配了一個(gè)空的字符串切片,其長度為 0,容量為 estSize,即預(yù)留了 estSize 個(gè)元素的存儲(chǔ)空間。在讀取數(shù)據(jù)文件時(shí),由于容量已經(jīng)預(yù)分配好了,append 函數(shù)不會(huì)進(jìn)行擴(kuò)容操作,從而減少了性能開銷。
需要注意的是,預(yù)估數(shù)據(jù)文件的大小應(yīng)該根據(jù)實(shí)際情況進(jìn)行調(diào)整,容量過小仍然會(huì)進(jìn)行擴(kuò)容操作,容量過大則會(huì)浪費(fèi)空間。
最后的總結(jié)
行切片操作時(shí),由于切片底層的數(shù)組容量是動(dòng)態(tài)變化的,因此容易出現(xiàn)內(nèi)存分配和釋放的性能問題。
對(duì)于大規(guī)模的數(shù)據(jù)處理場景,頻繁的內(nèi)存分配和釋放可能導(dǎo)致程序性能的大幅度下降,因此切片的內(nèi)存優(yōu)化是非常重要的。通過適當(dāng)?shù)卣{(diào)整切片的容量,可以有效地減少內(nèi)存分配和釋放的開銷,提高程序的運(yùn)行效率。
此外,內(nèi)存分配和釋放的開銷也會(huì)對(duì)垃圾回收的性能產(chǎn)生影響。如果程序中存在大量的內(nèi)存分配和釋放,將會(huì)導(dǎo)致垃圾回收器頻繁地進(jìn)行掃描和回收,從而降低程序的整體性能。因此,在開發(fā)過程中,我們需要盡可能地避免內(nèi)存分配和釋放的頻繁發(fā)生,尤其是在高性能的應(yīng)用場景中。
綜上所述,golang切片優(yōu)化內(nèi)存的重要性非常高,對(duì)于需要處理大規(guī)模數(shù)據(jù)的場景,進(jìn)行切片內(nèi)存優(yōu)化可以有效地提高程序的運(yùn)行效率和性能表現(xiàn)。
到此這篇關(guān)于Go語言實(shí)戰(zhàn)之切片內(nèi)存優(yōu)化的文章就介紹到這了,更多相關(guān)Go語言切片內(nèi)存優(yōu)化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- go中實(shí)現(xiàn)字符切片和字符串互轉(zhuǎn)
- Golang切片連接成字符串的實(shí)現(xiàn)示例
- Go語言之重要數(shù)組類型切片(slice)make,append函數(shù)解讀
- GO語言中創(chuàng)建切片的三種實(shí)現(xiàn)方式
- golang字符串切片去重的幾種算法
- 詳解golang的切片擴(kuò)容機(jī)制
- 一文詳解Go語言中切片的底層原理
- Go?語言中切片的三種特殊狀態(tài)
- 淺談go中切片比數(shù)組好用在哪
- 一文總結(jié)Go語言切片核心知識(shí)點(diǎn)和坑
- 深入剖析Go語言中數(shù)組和切片的區(qū)別
- Go切片的具體使用
相關(guān)文章
Golang 獲取文件md5校驗(yàn)的方法以及效率對(duì)比
這篇文章主要介紹了Golang 獲取文件md5校驗(yàn)的方法以及效率對(duì)比,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-05-05golang設(shè)置http response響應(yīng)頭與填坑記錄
這篇文章主要給大家介紹了關(guān)于golang設(shè)置http response響應(yīng)頭與填坑記錄的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08Golang反射獲取結(jié)構(gòu)體的值和修改值的代碼示例
這篇文章主要給大家介紹了golang反射獲取結(jié)構(gòu)體的值和修改值的代碼示例及演示效果,對(duì)我們的學(xué)習(xí)或工作有一定的幫助,感興趣的同學(xué)可以參考閱讀本文2023-08-08淺談Go語言中的結(jié)構(gòu)體struct & 接口Interface & 反射
下面小編就為大家?guī)硪黄獪\談Go語言中的結(jié)構(gòu)體struct & 接口Interface & 反射。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-07-07