性能和锁
Salted Fish 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
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
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
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
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
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
2
3
4
5
6
7
# 4.并发锁(Mutex)
# 4.1 零值是有效的
零值 sync.Mutex 和 sync.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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
嵌入写法,会使
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]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15