性能优化
最后更新于:2022-04-02 02:35:49
[TOC]
## 性能优化
### 减少[]byte的分配
```
slice = slice[:0]
```
所有的类型的Reset方法,均使用此方式。例如类型URI、Args、ByteBuffer、Cookie、RequestHeader、ResponseHeader等。
如:
```
// 清空 URI
func (u *URI) Reset() {
u.pathOriginal = u.pathOriginal[:0]
u.scheme = u.scheme[:0]
u.path = u.path[:0]
// ....
}
```
### 方法参数尽量用[]byte. 纯写场景可避免用bytes.Buffer
### make 初始化时,指定长度,可避免多次分配内存
`a := make([]int, 0, 10)`
在初始化`slice`时,若事先知道`capacity`的长度,可优化性能,避免多次内存分配
### 手段(去接口、内联)
注意:去接口不利于代码的可扩展性,需谨慎
> [参考网站](https://colobu.com/2019/12/31/small-changes-big-improvement/)
```
type DryFruit interface {
Name() string
Price() uint64
Family() string
Distribution() string
Increase()
}
type Chestnut struct {
name string
count uint64
}
// Name 名称.
func (c Chestnut) Name() string {
return c.name
}
//具体实现
//...
// Increase 数量加一
func (c *Chestnut) Increase() {
c.count++
}
// 性能差ad
type OriginGift struct {
mu sync.Mutex
dryFruit DryFruit //继承接口
}
// 性能比 OriginGift 更好
type ImprovedGift struct {
mu sync.Mutex
dryFruit *Chestnut //继承 具体实现
}
```
### 利用cpu缓存
> [参看](https://pengrl.com/p/9125/?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io)
```
type Foo struct {
a uint64
//_ [8]uint64
b uint64
//_ [8]uint64
}
func main() {
t1 := time.Now()
var group sync.WaitGroup
group.Add(2)
foo :=Foo{}
go func() {
for i := 0; i < 1000 * 1000; i++ {
atomic.AddUint64(&foo.a, 1)
}
group.Done()
}()
go func() {
for i := 0; i < 1000 * 1000; i++ {
atomic.AddUint64(&foo.b, 1)
}
group.Done()
}()
group.Wait()
since := time.Since(t1)
fmt.Printf("%+v\n", since)
}
```
1. 不利用cpu cache : 36.0059ms
2. 利用cpu cache取消注释 _ [8]uint64 : 6.0059ms
**使用场景**
* 适用于多个相邻的变量频繁被并发读写的场,缺点是padding增加了内存使用量开销
### 禁止没有timeout I/O
永远不要启动一个没有 timeout 的 IO 操作。多考虑:
```
SetDeadline
SetReadDeadline
SetWriteDeadline
```
### 优先使用 strconv 而不是 fmt
Bad
```
for i := 0; i < b.N; i++ {
s := fmt.Sprint(rand.Int())
}
//BenchmarkFmtSprint-4 143 ns/op 2 allocs/op
```
Good
```
for i := 0; i < b.N; i++ {
s := strconv.Itoa(rand.Int())
}
//BenchmarkStrconv-4 64.2 ns/op 1 allocs/op
```
### 避免字符串到字节的转换
Bad
```
for i := 0; i < b.N; i++ {
w.Write([]byte("Hello world"))
}
//BenchmarkBad-4 50000000 22.2 ns/op
```
Good
```
data := []byte("Hello world")
for i := 0; i < b.N; i++ {
w.Write(data)
}
//BenchmarkGood-4 500000000 3.25 ns/op
```
### 尽量初始化时指定 Map 容量
为 make() 提供 容量(hint) 信息尝试在初始化时调整 map 大小, 这减少了在将元素添加到 map 时增长 map 和 分配(allocations) 的开销 注意,map 不能保证分配 hint 个容量(hint) ,因此,即使提供了容量(hint),添加元素任然可以进行分配
Bad
```
m := make(map[string]os.FileInfo)
files, _ := ioutil.ReadDir("./files")
for _, f := range files {
m[f.Name()] = f
}
```
Good
```
files, _ := ioutil.ReadDir("./files")
m := make(map[string]os.FileInfo, len(files))
for _, f := range files {
m[f.Name()] = f
}
```
### 不要在文件操作循环中使用 defer
bad 方式会导致系统的文件描述符耗尽,因为在所有文件都被处理之前,没有文件会被关闭
bad
```
for _, filename := range filenames {
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close() // NOTE: risky; could run out of file
descriptors
// ...process f…
}
```
good
```
for _, filename := range filenames {
if err := doFile(filename); err != nil {
return err
}
}
func doFile(filename string) error {
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close()
// ...process f…
}
```
### 使用 strings.Builder 构建字符串
在Go中,字符串是不可变的。这意味着每次创建字符串时,都将分配新的内存
使用 `strings.Builder`在内部实现,它写入字节缓冲区。只有在生成器上调用String()时,才实际创建字符串,速度提高 近五倍
bad
```
a:="a"
a+="b"
fmt.Printf("%+v\n", a)
```
good
```
builder := strings.Builder{}
builder.WriteString("a")
builder.WriteString("b")
fmt.Printf("%+v\n", builder.String())
```
';