性能和锁

1991/6/26 规范

# 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
3
4
5

# 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
1
2
3
4
5

# 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
1
2
3
4
5

# 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
1
2
3
4
5
6

# 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
1
2
3
4
5
6
7

# 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
1
2
3
4
5
6
7

# 4.并发锁(Mutex)

# 4.1 零值是有效的

零值 sync.Mutexsync.RWMutex 是有效的。所以声明时不需要使用关键字new

Bad Good
mu := new(sync.Mutex) mu.Lock() var mu sync.Mutex mu.Lock()

# 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]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

嵌入写法,会使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]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

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!
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 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()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15