性能优化

最后更新于: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()) ```
';