Go 字符串中的潛在問題

在我之前的文章 Go 中我喜歡的東西 [1] 中提到過,我喜歡的 Go[2] 的東西其中之一就是它的字符串(通常還有切片)。從一個 Python 開發者的角度看,它們之所以偉大,是因爲創建它們時開銷很少,因爲它們通常不需要複製。在 Python 中,任何時候操作字符串都需要複製一部分或全部字符串,而 這很容易對性能造成影響 [3]。想要寫高性能的 Python 代碼需要謹慎考慮複製的問題。在 Go 中,幾乎所有的字符串操作都是不復制的,僅僅是從原字符串取一個子集(例如去除字符串首尾的空白字符),因此你可以更自由地操作字符串。這個機制可以非常直接地解決你的問題,並且非常高效。

(當然,不是所有的字符串操作都不復制。例如,把一個字符串轉換成大寫需要複製,儘管 Go 中的實現已經足夠智能,在不需要改變原字符串時 — 例如由於它已經是一個全大寫的字符串 — 可以規避掉複製。)

但是這個優勢也帶來了潛在的壞處,那些沒有開銷的子字符串使原來的整個字符串一直存在於內存中。Go 中的字符串(和切片)操作之所以內存開銷很少,是因爲它們只是底層存儲(字符串或切片底層的數組的真實數據)的一些部分的引用;創建一個字符串做的操作就是創建了一個新的引用。但是 Go(目前)不會對字符串數據或數組進行部分的垃圾回收,所以即使它一個很小的 bit 被其它元素引用,整個對象也會一直保持在內存中。換句話說,一個單字符的字符串(目前)足夠讓一個巨大的字符串不被 GC 回收。

當然,不會有很多人遇到這個問題。爲了遇到它,你需要處理一個非常龐大的原字符串,或造成大量的內存消耗(或者兩者都做),在這個基礎上,你必須創建那些不持久的字符串的持久的小子字符串(好吧,你是多麼希望它是非持久的)。很多使用場景不會復現這個問題;要麼你的原字符串不夠大,要麼你的子集獲取了大部分原字符串(例如你把原字符串進行了分詞處理),要麼子字符串生命週期不夠長。簡而言之,如果你是一個普通的 Go 開發者,你可以忽略這個問題。處理長字符串並且長時間維持原字符串的很小部分的人才會關注這個問題。

(我之所以關注到這個問題,是因爲一次我花了大量精力用盡可能少的內存寫 Python 程序,儘管它是從一個大的配置文件解析結果然後分塊儲存。這讓我聯想到了一些其他的事,如字符串的生命週期、限制字符串只複製一次,等等。然後我用 Go 語言寫了一個解析器,這讓我由重新考慮了一下這些問題,我意識到由於我的解析器截取出和維持的 bit 一直存在於內存中,從輸入文件解析出的龐大字符串也會一直存在與內存中。)

順便說一下,我認爲這是 Go 做了權衡之後的正確結果。大部分使用字符串的開發者不會遇到這個問題,而且截取子字符串開銷很小對於開發者來說用處很大。這種低開銷的截取操作也減輕了 GC 的負擔;當代碼使用大量的子字符串截取(像 Python 中那樣)時,你只需要處理固定長度的字符串引用就可以了,而不是需要處理長度變化的字符串。

當你的代碼遇到這個問題時,當然有明顯的解決方法:創建一個函數,通過把字符串轉換成 []byte 來 ” 最小化 “ 字符串,然後返回。這種方法生成了一個最小化的字符串,內存開銷是理論上最完美實現的只複製一次,而 Go 現在很容易就可以實現。

附加問題:strings.ToUpper() 等怎樣規避沒有必要的複製

所有的主動轉換函數像 ToUpper()ToTitle() 是用 strings.Map() 和 unicode 包 [4] 中的函數實現的。Map() 足夠智能,在映射的函數返回一個與已存在的 rune 不同的結果之前不會創建新的字符串。因此,你代碼中所有類似的直接使用 Map() 的地方都不會有內存開銷。


via: https://utcc.utoronto.ca/~cks/space/blog/programming/GoStringsMemoryHolding

作者:Chris Siebenmann[5] 譯者:lxbwolf[6] 校對:dingdingzhou[7]

本文由 GCTT[8] 原創編譯,Go 中文網 [9] 榮譽推出,發佈在 Go 語言中文網公衆號,轉載請聯繫我們授權。

參考資料

[1]

Go 中我喜歡的東西: https://utcc.utoronto.ca/~cks/space/blog/programming/GoThingsILike

[2]

Go: https://golang.org/

[3]

這很容易對性能造成影響: https://utcc.utoronto.ca/~cks/space/blog/python/StringSpeedSurprises

[4]

unicode 包: http://golang.org/pkg/unicode/

[5]

Chris Siebenmann: https://utcc.utoronto.ca/~cks/space/People/ChrisSiebenmann

[6]

lxbwolf: https://github.com/lxbwolf

[7]

dingdingzhou: https://github.com/dingdingzhou

[8]

GCTT: https://github.com/studygolang/GCTT

[9]

Go 中文網: https://studygolang.com/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/_ScaGaB8mr2aSyQjL-VIaA