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

85 阅读4分钟

这是我参与「第五届青训营」伴学笔记创作活动的第 3 天,通过学习高质量编程与性能调优实战的课程,我学习了对于go项目如何进一步进行性能优化,并了解了pprof工具的使用与工作原理。以下就是我的学习笔记。

一、课程概览

第一小节课讲的是高质量编程的内容,其中包括常见的编码规范、以及性能优化建议。对于代码规范,我曾阅读过掘金阅读打卡活动的一篇文章,有过细致的了解。性能优化建议主要从slice以及map预分配内存来介绍。

第二三小节,根据具体代码来讲解进行性能调优实战,主要介绍分析工具pprof的使用。总之课程主要围绕的最重要的一句话“高质量编程三原则:简单性、可读性、生产力。”

二、课程知识点回顾

高质量编程

代码格式:

使用gofmt包和goimports包可以实现自动格式化代码,比如:可以自动引入编码过程中使用到的包。

编码规范:

  • 我认为最最最重要的就是注释!通过注释可以帮助后面接手代码的人可以读懂代码,防止出现一些不可预知的错误,加快团队开发的效率。当然注释并不只是介绍代码的作用那么简单,我们还要解释清楚公共符号的含义与作用,要提供代码未表达出的上下文信息;
  • 要尽量去避免命名过于长,缩写用大写来命名,比如:webHTTP;
  • 对于函数名也不要过长,不要携带包名的上下文信息;
  • 包名也不要使用标准库的包名,比如用bufio而不是buf;
  • 控制流程尽量避免嵌套,必要时可以省略掉冗余的else。值得注意的是大多数的问题都出现在条件语句和循环语句中;
  • 可以使用fmt.Errorf来格式化需求(使用%w将一个错误关联至错误链中);使用error.New来创建错误信息;使用errors.Is来判定是否为一个特定的错误;在实际应用中要尽量避免使用panic;
  • recover只能当前的goroutine的被defer的函数中使用,嵌套无法生效;

性能优化建议:

  • 使用Benchmark,命令:go test -bench=,-benchmem,测试基准性能。可以看到每次执行花费的时间,内存大小和申请了几次的内存。
  • 对于map和slice尽量使用make()来初始化并提供提前预估好的容量,例如当slice的append发生之后的长度<=cap,将会直接利用原底层数组剩余的空间。当append之后的长度>cap 时,会分配一块更大的内存来容纳新的底层数组。因此,为了避免内存发生拷贝,如果能够知道最终的切片的大小,预先设置cap 的值能够避免额外的内存分配,获得更好的性能。
  • 为了底层数组得到释放,要使用copy来代替re-slice,因为copy指向一个新的底层数组,当origin不被引用后,内存就会被垃圾回收。
  • 对于字符串处理,最好使用strings.Builder或strings.Buffer,而不是“+”。因为每次“+”都会重新分配内存,而bytes.Buffer在转化为字符串时新申请了一块空间,strings.Builder直接将底层的[]byte转换成了字符串类型返回。
  • 使用空结构体可作为占位符使用,而且不占用任何内存空间,节省资源。

性能调优实战

搭建好pprof工具之后,运行项目,在浏览器中打开网址:http://localhost:6060/debug/pprof

就可以展示可用的程序运行采样数据,使用go tool pprof来启动采样。我们可以查看CPU、Heap堆内存、goroutine、mutex、block等问题出现的具体定位。

采样过程和原理

CPU 操作系统每10ms向进程发送一次SIGPROF信号,进程每次接收到SIGPROF会记录调用堆栈,每100ms读取已经记录的调用栈并写入输出流。

Heap 采样率可以进行修改,默认每分配512KB记录一次。采样程序通过内存分配器在堆上分配和释放的内存,来记录分配释放的大小和数量,计算方程: inuse = alloc - free

Goroutine&ThreadCreate goroutine记录所有用户发起且在运行中的goroutine;ThreadCreate记录程序创建的所有系统线程的信息。

Block&Mutex 阻塞耗时超过阀值得才会被记录;锁竞争只记录固定比例的锁操作。

三、总结

通过学习,我了解了pprof的使用以及一些采样的过程和原理,有了这个工具,可以让我们在测试的时候更加方便。不过好记性不如烂笔头,还是要通过练习使用,才能真正掌握。