Go高质量变成与性能调优 | 青训营笔记

83 阅读4分钟

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

Day4:Go高质量编程与性能调优

高质量编程

——编写的代码能够达到正确可靠、简洁清晰的目标

比如是否考虑边界、异常处理、易读易维护等

原则:简单性、可读性、生产力

  1. 代码格式:gofmt自动格式化,goimports工具(还可以自己更新依赖包)

  2. 注释:公共符号需要注释,要保证注释提供代码未表达出的上下文信息。注释要解释代码实现的过程和原因(补充一些上下文),解释代码出错的情况。

  3. 命名

    1. 变量:简洁胜于冗长;缩略词全大写,位于变量开头且不需要导出,用全小写(ServerHTTP, xmlServer);距离被使用的地方越远,需要的上下文越多。
    2. 函数:不携带包的上下文,因为他们经常成对出现。尽量短。当函数返回值类型与包名一致时,可以考虑在函数名中加入类型信息(如CutString等)
    3. package:只由小写字母构成,无大写字母和下划线。简短。不要与便准库同名。不使用常见变量名,尽量使用单数而非复数,谨慎使用缩写。
  4. 控制流程:线性原理,处理逻辑尽量走直线。避免嵌套,优先处理错误和特殊情况,尽早返回来减少运行次数。

  5. 错误和异常处理:简单错误用errors.New(),有格式化要求就用fmt.Errorf(错误链用%w)。与错误链路相关的可以有errors.is和errors.as的使用。不建议使用Panic,因为它会让程序直接崩溃,通常只用在启动阶段使用(init和main函数)

recover只能在被defer的函数中使用

...
defer func(){
    if e := recover(); e != nil {
        //恢复失败的逻辑
        err = fmt.Errorf("gitfs panic: %v\n%s", e, debug.Stack())//打印recover信息和调用栈 
    }
}()
...

defer语句的特性是后进先出,以下面的例子解释,输出结果为31

func main(){
    if true {
        defer fmt.Printf("1")
    }else {
        defer fmt.Printf("2")
    }
    defer fmt.Printf("3")
}

性能优化建议

工具

Go提供了支持基准性能测试的benchmark工具

go test -bench=. -benchmem

Slice预分配内存——提供cap信息

data := make([]int, 0, size)
优于
data := make([]int, 0)

copy替代reslice

copy(dst slice, sou slice)

通过大切片创造小切片时,使用copy会性能更优

map预分配内存

data := make(map[int]int, size)
优于
data := make(map[int]int)

如果我们可以提前预估的话,提供好size会极大的提高运行效率,因为扩容时map会有内存拷贝和rehash,这都是极大的消耗。

字符串拼接

METHOD 1:
s := ""
for {
    s += [string]
}
return s
METHOD 2var builder strings.Builder
for {
    builder.WriteString([string])
}
return builder.String()
METHOD 3var buf := new(bytes.Buffer)
for {
    buf.WriteString([string])
}
return buf.String()

从测试结果来看,后两种方法明显优于前两者

为什么?字符串是不可变类型通过+=操作会不断地分配新的空间(恰好放下新的字符串),所以这是极其低效的,而后两种会更好,而strings.Builder更好的原因在于,它直接把 []byte转换成字符串类型,没有新分配,但是bytes.buffer不一样,它会重新申请一块空间放最终生成的字符串。

能不能更优?可以使用builder.Grow([size]) or buf.Grow([size])来预分配内存

空结构体不占据任何内存

m := make(map[int]struct{}) —— 键值不占据任何空间,仅为占位符
优于
m := make(map[int]bool) —— 键值还有一个字节

可以用这个特性实现Set类型,因为只需要用到key,不需要用value

atomic包

atomic是通过硬件实现的,效率比通过操作系统实现的锁效率高

[有待后续学习]

性能调优工具

原则

  1. 依靠数据
  2. 定位最大瓶颈而非细枝末节
  3. 不要过早优化和过度优化

pprof工具

安装配置参考:www.manongjc.com/detail/54-t…

排查CPU

运行命令

go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10"//运行工具
top//展开详情
list ...//根据指定的正则表达式查找代码行
web//调用关系可视化

一些参数

如果flat == cun 函数中没有调用其它函数

如果flat == 0 函数中有调用其它函数

排查内存

命令

go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap"

一些数据

排查协程

go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/goroutine"

排查mutex / block

命令——修改相应的后缀即可

性能调优案例

——通常适用于逻辑相对复杂、需求量相对大的情况

基本概念

服务:能够单独部署,承载一定功能的程序

依赖:A的功能实现依赖于B的响应结果

调用链路:支持一个接口请求的相关请求服务集合及依赖关系

基础库:公共的包等

评估流程

建立评估手段——分析数据,定位瓶颈——重点项改造——优化结果验证

基础库优化 & Go语言优化(自由度高)