Go性能優化技巧 1/10

字符串(string)做爲一種不可變類型,在與字節數組(slice, [ ]byte)轉換時需付出 「沉重」 代價,根本緣由是對底層字節數組的複製。這種代價會在以萬爲單位的高併發壓力下迅速放大,因此對它的優化常變成 「必須」 行爲。數組

首先,須瞭解 string 和 [ ]byte 數據結構,並確認默認方式的複製行爲。數據結構

package main
 
import (
    "fmt"
)
 
func main() {
    s := "hello, world!"
    b := []byte(s)
    fmt.Println(s, b)
}

圖片描述

動態演示: https://asciinema.org/a/6up6gvgqo0v9zkjpusvyucg8g併發

從 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

相關文章
相關標籤/搜索