字符串(string)做爲一種不可變類型,在與字節數組(slice, [ ]byte)轉換時需付出 「沉重」 代價,根本緣由是對底層字節數組的複製。這種代價會在以萬爲單位的高併發壓力下迅速放大,因此對它的優化常變成 「必須」 行爲。數組
首先,須瞭解 string 和 [ ]byte 數據結構,並確認默認方式的複製行爲。數據結構
package main import ( "fmt" ) func main() { s := "hello, world!" b := []byte(s) fmt.Println(s, b) }
從 GDB 輸出結果可看出,轉換後 [ ]byte 底層數組與原 string 內部指針並不相同,以此可肯定數據被複制。那麼,如不修改數據,僅轉換類型,是否可避開復制,從而提高性能?函數
從 ptype 輸出的結構來看,string 可看作 [2]uintptr,而 [ ]byte 則是 [3]uintptr,這便於咱們編寫代碼,無需額外定義結構類型。如此,str2bytes 只需構建 [3]uintptr{ptr, len, len},而 bytes2str 更簡單,直接轉換指針類型,忽略掉 cap 便可。高併發
package main import ( "fmt" "strings" "unsafe" ) func str2bytes(s string) []byte { x := (*[2]uintptr)(unsafe.Pointer(&s)) h := [3]uintptr{x[0], x[1], x[1]} return *(*[]byte)(unsafe.Pointer(&h)) } func bytes2str(b []byte) string { return *(*string)(unsafe.Pointer(&b)) } func main() { s := strings.Repeat("abc", 3) b := str2bytes(s) s2 := bytes2str(b) fmt.Println(b, s2) }
用 unsafe 完成指針類型轉換,因此得自行爲底層數組生命週期作出保證。好在這兩個函數都很簡單,編譯器會完成內聯處理,並未發生逃逸行爲。性能
對比一下優化先後的性能差別。優化
package main import ( "strings" "testing" ) var s = strings.Repeat("a", 1024) func test() { b := []byte(s) _ = string(b) } func test2() { b := str2bytes(s) _ = bytes2str(b) } func BenchmarkTest(b *testing.B) { for i := 0; i < b.N; i++ { test() } } func BenchmarkTestBlock(b *testing.B) { for i := 0; i < b.N; i++ { test2() } }
性能提高明顯,最關鍵的是 zero-garbage。ui
最新動態,請掃碼關注spa