这是我参与「第五届青训营 」笔记创作活动的第3天
Go高质量编码
编码规范
- 代码格式
- 注释
- 命名规范
- 控制流程
- 错误和异常处理:
recover()用于处理panic,只能在被defer的函数中使用,嵌套无法生效,只在当前goroutine生效
多个defer语句是后进先出
性能优化建议
Benchmark
编写好示例代码:
//fib.go
func Fib(n int) int {
if n < 2 {
return n
}
return Fib(n-1) + Fib(n-2)
}
//fib_test.go
func BenchmarkFib(b *testing.B) {
for n := 0; n < b.N; n++ {
Fib(10)
}
}
然后输入指令
go test -bench .
结果如下:
其中:
BenchmarkFib-8:前面是函数名,后面是GOMAXPROCS(Go1.5后默认为CPU核数)4777920:总共执行次数,即b.N的值253.6 ns/op:每次执行花费253.6ns
Slice
预分配内存
尽可能在使用make()初始化切片时提供容量信息
大内存未释放
-
在已有切片基础上创建切片,不会创建新的底层数组
-
场景:
- 原切片较大,代码在原切片基础上新建小切片
- 原底层数组在内存中有引用,得不到释放
-
可使用
copy代替re-slice
Map
预分配内存
字符串处理
使用strings.Builder
-
常见拼接方式:
性能差异:
原因:
- 每次使用
+会开辟新的空间,会涉及内存分配,性能更差一些 bytes.Buffer转化为字符串时,重新分配了一块空间strings.Builder直接将底层[]byte转化为了字符串类型返回- 使用预分配可以进一步提高性能:
空结构体
使用空结构体节省内存
-
空结构体struct{}不占据任何的内存空间
-
可作为任何场景下的占位符使用
- 节省资源
- 空结构体本身具有很强的语义,即这里不需要任何值,仅作为占位符
Atomic包
如何使用atomic包:
type atomicCounter struct {
i int32
}
func AtomicAddOne(c *atomicCounter) {
atomic.AddInt32(&c.i, 1)
}
原子操作比加锁性能好很多
原因:
- 锁是通过操作系统来实现,属于系统调用
- atomic是通过硬件实现,效率比锁高
- sync.Mutex应该用来保护一段逻辑,不仅仅用于保护一个变量
- 对于非数值操作,可以使用
atomic.Value,能承载一个interface{}
性能调优实战
简介
性能优化原则
- 要依靠数据而不是猜测
- 要定位最大瓶颈而不是细枝末节
- 不要过早优化
- 不要过度优化
pprof
场景
- 希望知道应用在什么地方耗费了多少CPU、Memory
- pprof是用于可视化和分析性能数据的工具
功能简介
实战
[项目代码] github.com/wolfogre/go…
使用命令采集数据
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=10
输入top命令查看cup占用情况:
可以发现是tiger.Eat方法占用最大,我们来看一下代码:
是一个巨大的空循环,这就把问题排查出来了
思考
- Flat == Cum时,函数中没有调用其他函数
- Flat == 0时,函数中只有其他函数的调用
Heap内存分析
go tool pprof -http=localhost:8080 "http://localhost:6060/debug/pprof/heap"
Gorounine泄露
go tool pprof -http=localhost:8080 "http://localhost:6060/debug/pprof/goroutine"
火焰图:
- 由上到下表示调用顺序
- 每一块代表一个函数越长代表CUP占用时间越长
- 火焰图是动态的,支持点击块进行分析