歡迎訪問我的個(gè)人網(wǎng)站獲取更佳排版體驗(yàn): https://pengrl.com/p/31544/ (Go語(yǔ)言中[]byte和string類型相互轉(zhuǎn)換時(shí)的性能分析和優(yōu)化 | yoko blog )
我們?cè)谑褂肎o語(yǔ)言時(shí)即舌,經(jīng)常涉及到[]byte和string兩種類型間的轉(zhuǎn)換葛圃。本篇文章將討論轉(zhuǎn)換時(shí)的開銷肪获,Go編譯器在一些特定場(chǎng)景下對(duì)轉(zhuǎn)換做的優(yōu)化灶壶,以及在高性能場(chǎng)景下,我們自己如何做相應(yīng)的優(yōu)化夭委。
[]byte其實(shí)就是byte類型的切片旅薄,對(duì)應(yīng)的底層結(jié)構(gòu)體定義如下(在runtime/slice.go
文件中)
type slice struct {
array unsafe.Pointer
len int
cap int
}
string對(duì)應(yīng)的底層結(jié)構(gòu)體定義如下(在runtime/string.go
文件中)
type stringStruct struct {
str unsafe.Pointer
len int
}
可以看到它們內(nèi)部都有一個(gè)指針類型(array或str)沥阱,指向真實(shí)數(shù)據(jù)。另外還有一個(gè)len字段昔头,標(biāo)識(shí)數(shù)據(jù)的長(zhǎng)度饼问。
slice多了一個(gè)cap字段,表示容量大小揭斧。當(dāng)要往slice尾部追加數(shù)據(jù)而空余容量又不夠時(shí)莱革,會(huì)重新分配更大的內(nèi)存塊,將當(dāng)前內(nèi)存塊的內(nèi)容拷貝至新內(nèi)存塊,再在新內(nèi)存塊做追加盅视。
slice變量間做賦值操作時(shí)捐名,只是修改指針指向,不會(huì)拷貝真實(shí)數(shù)據(jù)闹击。string變量間賦值也是同樣的道理镶蹋。
但是[]byte和string相互轉(zhuǎn)換,就需要重新申請(qǐng)內(nèi)存并拷貝內(nèi)存了赏半。因?yàn)镚o語(yǔ)義中贺归,slice的內(nèi)容是可變的(mutable
),而string是不可變的(immutable
)除破。如果他們底部指向同一塊數(shù)據(jù)牧氮,那么由于slice可對(duì)數(shù)據(jù)做修改琼腔,string就做不到immutable
了瑰枫。
[]byte和string互轉(zhuǎn)時(shí)的底層調(diào)用分別對(duì)應(yīng)runtime/string.go
中stringtoslicebyte
和slicebytetostring
兩個(gè)函數(shù)。
那么如果我們想省去申請(qǐng)和拷貝內(nèi)存的開銷呢丹莲?
來看runtime/string.go
中slicebytetostringtmp
和stringtoslicebytetmp
兩個(gè)函數(shù)光坝,如下:
func slicebytetostringtmp(b []byte) string {
// Return a "string" referring to the actual []byte bytes.
// This is only for use by internal compiler optimizations
// that know that the string form will be discarded before
// the calling goroutine could possibly modify the original
// slice or synchronize with another goroutine.
// First such case is a m[string(k)] lookup where
// m is a string-keyed map and k is a []byte.
// Second such case is "<"+string(b)+">" concatenation where b is []byte.
// Third such case is string(b)=="foo" comparison where b is []byte.
if raceenabled && len(b) > 0 {
racereadrangepc(unsafe.Pointer(&b[0]),
uintptr(len(b)),
getcallerpc(unsafe.Pointer(&b)),
funcPC(slicebytetostringtmp))
}
return *(*string)(unsafe.Pointer(&b))
}
func stringtoslicebytetmp(s string) []byte {
// Return a slice referring to the actual string bytes.
// This is only for use by internal compiler optimizations
// that know that the slice won't be mutated.
// The only such case today is:
// for i, c := range []byte(str)
str := (*stringStruct)(unsafe.Pointer(&s))
ret := slice{array: unsafe.Pointer(str.str), len: str.len, cap: str.len}
return *(*[]byte)(unsafe.Pointer(&ret))
}
通過unsafe.Pointer
直接做指針類型的轉(zhuǎn)換。
注釋中也說得很清楚了甥材。
stringtoslicebytetmp
調(diào)用的前提是保證返回的[]byte之后不會(huì)被修改盯另,只用于編譯器內(nèi)部?jī)?yōu)化,目前唯一的場(chǎng)景是在for loop中將string轉(zhuǎn)換成[]byte做遍歷操作時(shí)洲赵,比如 for i, c := range []byte(str)
slicebytetostringtmp
調(diào)用的前提其實(shí)也是類似鸳惯,保證返回的string在生命周期結(jié)束之前,[]byte不會(huì)被修改叠萍,也是只用于編譯器內(nèi)部?jī)?yōu)化芝发,目前有三種場(chǎng)景:
- 假設(shè)有一個(gè)key為string的map遍歷m,你想使用[]byte類型的變量k做查找操作苛谷,比如
m[string(k)]
- 做字符串拼接操作時(shí)辅鲸,比如
<"+string(b)+">
,其中b是[]byte類型 - []byte類型和常量字符串做比較操作腹殿,比如
string(b)=="foo"
由于以上兩個(gè)函數(shù)是不暴露給Go用戶的独悴,所以如果我們?cè)谝恍└咝阅軋?chǎng)景想要做類似優(yōu)化時(shí),可以通過unsafe.Pointer
自己做類似實(shí)現(xiàn)锣尉,當(dāng)然刻炒,前提是保證數(shù)據(jù)是immutable的。
參考鏈接: