Go高性能编程与性能调优 | 青训营笔记

73 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天

通过今天的学习,我收获到了

  1. 如何编写更简洁清晰的代码
  2. 常用Go语言程序优化手段
  3. 熟悉Go程序性能分析工具
  4. 了解了工程中性能优化的原则和流程

----------------------------学习笔记------------------------------

1. 高质量编程

1.1 高质量编程简介

image.png

  • 正确性:是否考虑各种边界条件,错误的调用是否能够处理
  • 可靠性:异常情况或者错误的处理策略是否明确,依赖的服务出现异常是否能够处理
  • 简洁:逻辑是否简单,后续调整功能或新增功能是否能够快速支持
  • 清晰:其他人在阅读理解代码的时候是否能清楚明白,重构或者修改功能是否不会担心出现无法预料的问题

编程原则:

image.png

1.2 编码规范

1.2.1 代码格式

image.png

1.2.2 注释

image.png

image.png

小结:

  • 代码是最好的注释
  • 注释应该提供代码未表达出的上下文信息

1.2.3 命名规范

变量命名规范:

image.png

image.png

函数命名规范:

image.png

image.png

包命名规范:

image.png

小结:

  • 核心目标是降低阅读理解代码的成本
  • 重点考虑上下文信息,设计简洁清晰的名称

1.2.4 控制流程

image.png

image.png

image.png

小结:

  • 线性原理,处理逻辑尽量走直线,避免复杂的嵌套分支
  • 正常流程代码沿着屏幕向下移动
  • 提升代码可维护性和可读性
  • 故障问题大多出现在复杂的条件语句和循环语句中

1.2.5 错误和异常处理

image.png

image.png

image.png

image.png

image.png

image.png

image.png

小结:

  • error尽可能提供简明的上下文信息链,方便定位问题
  • panic用于真正异常的情况
  • recover生效范围,在当前goroutine的被defer的函数中生效

知识点:defer生命的语句执行顺序,其实是放入了一个栈中,程序执行结束之前顺序出栈

package main

import "log"

func main() {
   if true {
      defer log.Println("1")
   } else {
      defer log.Println("2")
   }
   defer log.Println("3")
}

运行结果: 31

1.3 性能优化建议

注意:性能优化的前提是满足正确可靠、简洁清晰等质量因素. 性能优化是综合评估,有时候时间效率和空间效率可能对立

Benchmark:Benchmark是Go语言提供了支持基准性能测试的benchmark工具

以下代码是利用BenchmarkFib()方法进行基准测试

package benchmark

import "testing"

func Fib(n int) int {
   if n < 2 {
      return n
   }
   return Fib(n-1) + Fib(n-2)
}

func BenchmarkFib(b *testing.B) {
   for i := 0; i < b.N; i++ {
      Fib(10)
   }
}

结果:

goarch: amd64
pkg: github.com/wolfogre/go-pprof-practice/benchmark
cpu: Intel(R) Core(TM) i5-10200H CPU @ 2.40GHz
BenchmarkFib
BenchmarkFib-8           4894159               238.8 ns/op
PASS

Process finished with the exit code 0
  • BenchmarkFib是测试函数名,-8表示GOMAXPROCS的值(默认为CPU核数)
  • 4894159表示一共执行了4894159次,即b.N的值
  • 238.8 ns/op表示每次执行花费238.8ns

预分配内存(此处没有实际测试,参考课件代码)

slice预分配内存:

image.png

image.png

map预分配内存:

image.png

image.png

字符串拼接优化

package benchmark

import (
   "strings"
   "testing"
)

func Plus(n int, str string) string {
   s := ""
   for i := 0; i < n; i++ {
      s += str
   }
   return s
}

func StrBuilder(n int, str string) string {
   var builder strings.Builder

   for i := 0; i < n; i++ {
      builder.WriteString(str)
   }
   return builder.String()
}

func BenchmarkPlus(b *testing.B) {
   for i := 0; i < b.N; i++ {
      Plus(10, "a")
   }
}

func BenchmarkStrBuilder(b *testing.B) {
   for i := 0; i < b.N; i++ {
      StrBuilder(10, "a")
   }
}

两个测试函数运行结果:

BenchmarkPlus
BenchmarkPlus-8          4732092               253.0 ns/op

BenchmarkStrBuilder
BenchmarkStrBuilder-8           17753370                56.61 ns/op

很明显,使用 + 运算符拼接字符串的效率远不及strings.Builder拼接字符串.

其实, 当使用 + 拼接 2 个字符串时,生成一个新的字符串,那么就需要开辟一段新的空间,新空间的大小是原来两个字符串的大小之和。拼接第三个字符串时,再开辟一段新空间,新空间大小是三个字符串大小之和,以此类推

image.png

为什么sringbuilder会比bytebuffer更快一些,以下是二者的源码:

image.png

空结构体

image.png

atomic包

image.png

image.png

image.png