Uber Go 規範 -三-: 性能和鎖
Uber 是一家美國硅谷的科技公司,也是 Go 語言的早期 adopter。其開源了很多 golang 項目,諸如被 Gopher 圈熟知的 zap、jaeger 等。2018 年年末 Uber 將內部的 Go 風格規範 開源到 GitHub,經過一年的積累和更新,該規範已經初具規模,並受到廣大 Gopher 的關注,本文是對規範的整理
https://github.com/xxjwxc/uber_go_guide_cn
1. 優先使用 strconv 而不是 fmt
下面代碼是將int
轉成字符串,使用兩種方式進行性能對比。
1.1 反面示例
for i := 0; i < b.N; i++ {
s := fmt.Sprint(rand.Int())
}
// 壓測 每次操作 耗時:143納秒,進行2次內存操作
// BenchmarkFmtSprint-4 143 ns/op 2 allocs/op
1.2 推薦用法
for i := 0; i < b.N; i++ {
s := strconv.Itoa(rand.Int())
}
// 壓測 每次操作 耗時:64.2納秒,進行1次內存操作
// BenchmarkStrconv-4 64.2 ns/op 1 allocs/op
2. 避免字符串到字節的轉換
不要反覆從固定字符串創建字節
slice
。相反,應該只執行一次轉換並保存結果到變量。
2.1 反面示例
for i := 0; i < b.N; i++ {
w.Write([]byte("Hello world"))
}
// 壓測 每次操作 耗時:22.2 納秒
// BenchmarkBad-4 50000000 22.2 ns/op
2.2 推薦用法
data := []byte("Hello world")
for i := 0; i < b.N; i++ {
w.Write(data)
}
// 壓測 每次操作 耗時:3.25 納秒
// BenchmarkGood-4 500000000 3.25 ns/op
3. 指定 map、slice 容量
指定容量, 能提升代碼性能,特別是在追加切片時
3.1 反面示例
for n := 0; n < b.N; n++ {
data := make([]int, 0)
for k := 0; k < size; k++{
data = append(data, k)
}
}
// BenchmarkBad-4 100000000 2.48s
3.2 推薦用法
for n := 0; n < b.N; n++ {
data := make([]int, 0, size)
for k := 0; k < size; k++{
data = append(data, k)
}
}
// BenchmarkGood-4 100000000 0.21s
4. 併發鎖 (Mutex
)
4.1 零值是有效的
零值 sync.Mutex
和 sync.RWMutex
是有效的。所以聲明時不需要使用關鍵字new
。
4.2 在結構體中使用
如果使用結構體指針,mutex
應作爲結構體的非指針字段。即使該結構體不被導出,也不能直接把 mutex
嵌入到結構體中。
1. 反面示例
type SMap struct {
sync.Mutex // 這裏是直接嵌入
data map[string]string
}
func NewSMap() *SMap {
return &SMap{
data: make(map[string]string),
}
}
func (m *SMap) Get(k string) string {
m.Lock()
defer m.Unlock()
return m.data[k]
}
嵌入寫法,會使
Lock
和Unlock
成爲SMap
導出方法,在外面可直接使用。
2. 推薦用法
type SMap struct {
mu sync.Mutex
data map[string]string
}
func NewSMap() *SMap {
return &SMap{
data: make(map[string]string),
}
}
func (m *SMap) Get(k string) string {
m.mu.Lock()
defer m.mu.Unlock()
return m.data[k]
}
mutex 及其方法是
SMap
的實現細節,對其調用者不可見。
5. 使用sync/atomic
執行原子操作
使用 sync/atomic 包的原子操作對原始類型 (int32
, int64
等)進行操作,因爲很容易忘記使用原子操作來讀取或修改變量。
go.uber.org/atomic 通過隱藏基礎類型爲這些操作增加了類型安全性。此外,它包括一個方便的atomic.Bool
類型。
5.1 反面示例
type foo struct {
running int32 // atomic
}
func (f* foo) start() {
if atomic.SwapInt32(&f.running, 1) == 1 {
// already running…
return
}
// start the Foo
}
func (f *foo) isRunning() bool {
return f.running == 1 // race!
}
5.2 推薦示例
type foo struct {
running atomic.Bool
}
func (f *foo) start() {
if f.running.Swap(true) {
// already running…
return
}
// start the Foo
}
func (f *foo) isRunning() bool {
return f.running.Load()
}
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/6z7kbG-st5AI7czC4XoHNQ