今天打卡第5天,主要内容为 : 高质量编程与性能调优实战
高质量编程
简介
高质量 : 编写的代码能够达到正确可靠,简洁清晰的目标可称为高质量代码
- 各种边界条件是否考虑完备
- 异常情况处理,稳定性保证
- 易读易维护
编程原则 :
- 简单性 : 以简单清晰的逻辑编写代码
- 可读性 :确保代码可读
- 生产力 : 团队整体的工作效率非常重要
编程规范
如何编写高质量的Go代码
-
代码格式
-
推荐使用gofmt自动格式化代码,go官方提供的工具,能自动格式化Go语言代码为官方统一风格
-
goimports : gofmt + 依赖包管理
自动增删依赖的包引用,将依赖包按字母序排序并分类
-
-
注释
注释应该解释
- 代码作用
- 代码如何做的
- 代码实现的原因
- 代码什么时候会出错
-
命名规范
-
控制流程
-
避免嵌套,保持正常流程清晰
如 : 将
if foo { return x } else { return nil }改为 :
if foo { return x } return nil就能去掉冗余的else
-
保持正常代码路径为最小缩进
- 优先处理错误情况/特殊情况,尽早返回减少嵌套。
-
-
错误和异常处理
小结 :
- error尽可能提供简明的上下文信息链,方便定位问题
- panic用于真正异常的情况
- recover生效范围,在当前goroutine 的被defer的函数中生效
性能优化指南
性能优化建议-Benchmark
- 性能表现需要用实际数据衡量
- Go语言提供了支持基准性能测试的benchmark工具
语法 :
go test -bench=. -benchmem
例 :
fib.go :
package main
func Fib(n int) int {
if n < 2 {
return n
}
return Fib(n-1) + Fib(n-2)
}
fib_test.go
package main
import "testing"
func BenchmarkName(b *testing.B) {
for i := 0; i < b.N; i++ {
Fib(10)
}
}
运行效果 :
性能优化建议-Slice
slice预分配内存 :
- 切片本质是一个数组片段的描述 :
- 包括数组指针
- 片段的长度
- 片段的容量(不改变内存分配情况下的最大长度)
- 切片操作并不复制切片指向的元素
- 创建一个新的切片会复用原来的底层数组
type slice struct {
array unsafe.Pointer
len int
cap int
}
性能优化建议-Map
预分配 -> 性能优化
例:
func NoPreAlloc(size int) {
data := make([]int, 0)
for k := 0; k < size; k++ {
data = append(data, k)
}
}
改为 :
func NoPreAlloc(size int) {
data := make([]int, 0, size)
for k := 0; k < size; k++ {
data = append(data, k)
}
}
Map预分配内存
- 不断向map中添加元素的操作会触发map的扩容
- 提前分配好空间可以减少内存拷贝和Rehash的消耗
- 建议根据实际需求提前预估好需要的空间
性能优化建议 - 字符串处理
-
常见的字符串拼接方式
1.+=
func Plus(n int,str string) string { s := "" for i:=0; i<n;i++ { s += str } return s }2.使用strings.Builder
func StrBuilder(n int, str string) string { var builder strings.Builder for i := 0; i < n; i++ { builder.WriteString(str) } return builder.String() }3.使用strings.Builder
func ByteBuffer(n int,str string) string { buf := new(bytes.Buffer) for i:=0;i<n;i++ { buf.WriteString(str) } return buf.String() } -
使用+拼接的性能最差,string.Builder,bytes.Buffer相近,string.Buffer更快
-
分析 :
- 字符串在go中是不可变的类型,占用内存大小是固定的
- 使用+每次都会重新分配内存
- strings.Builder,bytes.Buffer底层都是[]byte数组
- 内存扩展策略,不需要每次拼接重新分配内存
使用strings.Builder的效率是最快的,实现内存预分配能再加快效率。
性能优化建议-空结构体
-
使用空结构体能节省空间,空结构体struct{}实例不占用任何的内存空间
-
课作为各种场景下的占位符使用
- 节省资源
- 空结构体本身具有很强的语义,即这里不需要任何值,仅作为占位符
func EmptyStructMap(n int) {
m := make(map[int]struct{})
for i := 0; i < n; i++ {
m[i] = struct{}{}
}
}
func BoolMap(n int) {
m := make(map[int]bool)
for i:=0;i<n;i++ {
m[i] = false
}
}
使用场景 :
- 实现Set,可以考虑用map来替代。只使用Map中的键。
- 对于这个场景,只使用Map中的键,而不需要用到值
- 即便是map的值设置成bool类型,也会多占用一个空间
- 一个开源实现 : github.com/deckarep/go…
性能优化建议 - atomic包
如何使用atomic包 :
性能优化建议 -- 总结
- 避免常见的性能陷阱可以保证大部分程序的性能
- 普遍应用代码,不要一味的追求程序的性能
- 越高级的性能优化手段越容易出现问题
- 再满足正确可靠,简洁清晰的质量要求下提高程序性能。
性能调优分析工具
性能调优原则 :
- 要依靠数据不是猜测
- 要定位最大瓶颈而不是细节技术
- 不要太早优化
- 不要过度优化
工具pprof
- 希望知道应用再什么地方消耗量多少的CPU,Memory
- pprof是用于可视化和分析性能分析数据的工具
功能简介
实战
搭建pprof实践项目
- Github(来自Wolfogre)
- 地址 : github.com/wolfogre/go…
- 项目提前埋入一些炸弹代码,产生可观测的性能问题
前置准备 :
- 下载项目代码,能够编译运行
- 会占用1CPU核心和超过1GB的内存
运行效果(不断打印日志) :
保持程序运行,打开浏览器访问 http://localhost:6060/debug/pprof/,可以看到如下页面:
任务管理器窗口 :
使用 go tool pprof
go tool pprof http://localhost:6060/debug/pprof/profile
输入 top 命令,查看 CPU 占用较高的调用:
- flat : 当前函数本身的执行耗时
- flat% : flat占CPU总时间的比例
- sum% : 上面每一行的flat%总和
- cum : 指当前函数本身加上其调用函数的总耗时
- cum% : cum占CPU总时间的比例
- Flat == Cum : 函数中没有调用其它函数
- Flat == 0 : 函数中只有其它函数的调用
输入list Eat命令 ,查看问题具体在代码的哪一个位置::
- 命令 : web
- 调用关系可视化