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.Mutexsync.RWMutex 是有效的。所以聲明時不需要使用關鍵字new

WIg6hI

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]
}

嵌入寫法,會使LockUnlock 成爲 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