高质量编程与性能调优实战 | 青训营

161 阅读8分钟

这是我参与「第六届青训营 -后端场」笔记创作活动的的第3篇笔记。


性能优化的作用

经过我这些天的学习,我先来说说我了解到的性能优化的好处。

  • 提高响应速度:性能优化可以减少代码的执行时间,从而提高系统的响应速度。通过使用一些方法,可以减少不必要的计算和等待时间,更快地结束任务。
  • 降低资源消耗:性能优化可以减少代码对系统资源的需求,如CPU、内存和磁盘空间等。
  • 优化算法和数据结构:选择合适的算法和数据结构是性能优化这很关键。比如说,选择具有较低时间复杂度的算法可以显著提高代码的执行效率。
  • 减少网络延迟:对于涉及网络通信的代码项目,性能优化可以减少网络延迟,提高数据传输的速度和效率。
  • 内存管理和垃圾回收:性能优化至有助于进行合理的内存管理和垃圾回收。

图片优化

  1. 选择合适的图片格式:根据不同的需求和场景,选择合适的图片格式,如JPEG、PNG、WebP等。JPEG适用于照片等真彩色图片,而PNG适用于有透明度要求的图片,WebP是一种新兴的图片格式,在保持图片质量的同时能大幅减小文件大小。
  2. 压缩图片大小:使用专业的图片压缩工具,如ImageOptim、TinyPNG等,压缩图片的文件大小,以减少加载时间。同时,可以设置适当的图片尺寸以及裁剪不必要的部分,进一步减小图片的大小。
  3. 使用图片懒加载:对于页面上的大量图片,可以采用图片懒加载技术,即在用户滚动到可见区域时再加载图片,以提高初始加载速度。

前端资源优化

  1. 合并和压缩CSS和JavaScript文件:将多个CSS和JavaScript文件合并为一个文件,并使用压缩工具对文件进行压缩,以减少HTTP请求和文件大小。
  2. 使用浏览器缓存:通过设置适当的缓存策略,让浏览器缓存静态资源文件,并在后续访问中直接从缓存中加载文件,减少网络请求。
  3. 异步加载脚本:将一些不影响页面渲染的脚本标记为异步加载,以避免阻塞页面加载。

数据请求优化

  1. 减少HTTP请求次数:合并多个请求,减少不必要的网络请求次数。例如,可以将多个API请求合并为一个请求,或者使用数据缓存技术,避免重复请求相同的数据。
  2. 使用CDN加速:将静态资源部署到CDN(内容分发网络),使用户能够从离自己最近的节点获取资源,减少网络延迟和提高加载速度。
  3. 数据压缩:对于传输的大量数据,可以使用数据压缩算法,如Gzip,减小数据大小,提高传输效率。

性能优化建议

slice预分配内存

尽可能在使用make()初始化切片时提供容量信息,能够避免底层的内存分配次数

指定slice的时候没有指定大小

image.png

在初始化的时候指定了大小

image.png

如果我们提前分配的话,它的执行时间大概是原来的三分之之一左右(缩短时间)

image.png

如果容量足够,新增元素直接放进数组里。

但若容量不够,底层数组先有扩容的操作,然后再把实际内容添加到数组中。

由于操作耗时,容易造成性能差,为了避免内存发生copy的过程,

所以先在初始化时设置合适的值,避免额外的分配,从而获得更好的性能。

在已有切片基础上创建切片,不会创建底层数组,这会导致

  • 原切片较大,代码在原切片基础上新建小切片
  • 原底层数组在内存中有引用,得不到释放

所以我们可以使用copy代替re-slice

map预分配内存

不断向map中添加元素的操作会触发 map 的扩容 根据实际需求提前预估好需要的空间 提前分配好空间可以减少内存copyRehash 的消耗

字符串处理

字符串在 Go 语言中是不可变类型,占用内存大小是固定的

使用 + 拼接 字符串每次都会重新开辟新空间

+ : 最慢

strings.Builder:最快,内存以倍数申请,底层是[]byte数组,直接将底层的 []byte 转换成了字符串类型返回。

bytes.Buffer :较快,内存以倍数申请,底层是 []byte 数组,转化为字符串重新申请空间存放生成的字符串变量。

使用空结构体节省内存

空结构体不占据内存空间,可作为占位符使用。

例如Set,Go 语言标准库没有提供 Set 的实现,通常使用 map 来代替。

对于集合场景,只需要用到 map 的键而不需要值。

实战案例

下载测试代码

在终端里运行go get 中可以获取测试程序, 注意加上 -d 参数,避免下载后自动安装:

Github

 go get -d github.com/wolfogre/go-pprof-practice
 cd $GOPATH/src/github.com/wolfogre/go-pprof-practice

go get 下载包时提示 connection failed because connected host has failed to respond 解决方法:

使用 git clone 下载

 git clone https://github.com/wolfogre/go-pprof-practice

克隆报错fatal: unable to access 'https://github.com/xiaogao67/gin-cloud-storage.git/': SSL certificate problem: unable to get local issuer certificate 解决方法:

当通过HTTPS访问Git远程仓库的时候,如果服务器上的SSL证书未经过第三方机构认证便会报错。原因是未知的没有签署过的证书意味着可能存在很大的风险。解决办法即通过以下命令将git中的sslverify关掉。

 git config --global http.sslverify false

对代码进行编译运行

 go mod init
 go mod tidy

再运行(这个程序没有任何外部依赖)

 go build
 ./go-pprof-practice

运行后进行测试

运行后会不断打印日志,以下是内容(意义不大,不必细看):

image.png

这一步用来测试资源吃否吃紧,能否扛住一分钟,如果可以,便可以进行下一步了。

使用pproof

保持程序运行,打开浏览器访问 http://localhost:6060/debug/pprof/

可以看到以下内容:

image.png

排场

go tool pprof命令不但可以使用交互式终端,也可以使用web进行可视化分析,此外可以直接将数据生成svg图片,进行静态的分析。

 go tool pprof http://localhost:6060/debug/pprof/profile

image.png

根据上图左边的数量可以看出需要排场的方面。

后续例如,排场内存占比时,此时URL为heap

 go tool pprof http://localhost:6060/debug/pprof/heap

排场排查频繁内存回收时,为allocs

 go tool pprof http://localhost:6060/debug/pprof/allocs

排查锁的争用时,用mutex

 go tool pprof http://localhost:6060/debug/pprof/mutex

其实也就是修改尾缀

top 和list 命令

  • 输入 top 命令,我们可以查看 CPU 占用较高的调用。

可以看到

 github.com/wolfogre/go-pprof-practice/animal/felidae/tiger.(*Tiger).Eat

这句是代码中cpu占过高的地方

  • 输入 list Eat命令,我们可以查看代码具体的位置。

image.png 定位到24行,可见有个一百亿次空循环占用了大量 CPU 时间。

图形化显示调用栈信息

访问 graphviz 官网寻找适配自己操作系统的安装包,或者输入命令下载。

(这里我的电脑型号用不了这个命令,也可能有其他操作,但是我没试成功,我选择了直接在官网下载)

web命令

输入命令后会弹出一个.svg文件的弹框,选择用浏览器打开。

image.png

会看到类似这样的框图

//我刚开始准备做这个实验的时候,web命令一直报错,先做了其他选题。

//回头做这个实验,莫名其妙就可以用了,原理我还不清楚。

由框图的大小易知tiger.(*Tiger).Eat 函数CPU占比很大。

  • 输入exit,可退出交互功能
  • 修复过程可以优化代码,鉴于实验学习先简单注释掉。

筛选GC日志

cd进入项目所在目录,在终端输入命令:

 GODEBUG=gctrace=1 ./go-pprof-practice | grep gc

以上为MacOS/Linux 上的命令。

本人用的Windows,使用的命令为:

 set GODEBUG=gctrace=1 
 go run main.go | findstr gc

截取了一部分示例

image.png

提一下实验里排查到dog.run 这一块

存在无意义的内存申请,而这个函数又会被频繁调用,就导致了程序不停地进行 GC

当我们把问题代码注释掉

 func (d *Dog) Run() {
     log.Println(d.Name(), "run")
     //_ = make([]byte, 16 * constant.Mi)
 }

image.png

image.png

就会发现短时间内未打印 GC 日志了。

image.png

排查分析

框图大也并不代表原因直接出在里边,例如:

image.png

我们可以通过追根溯源,发现

 github.com/wolfogre/go-pprof-practice/animal/canidae/wolf.(*Wolf).Drink

是这一句不断地在创建没有必要的协程

协程泄露

我们发现大量的协程调用栈最终都会指向 runtime.gopark 函数。

这个函数的作用就是将协程变成等待状态 _Gwaiting,随着 goroutine 越来越多最终耗尽内存,内存溢出。

mutex阻塞分析

在分析报告中我们发现互斥锁带来的协程休眠时间是30s 左右

cpu利用率不高,浪费了大量的时间

所以需要调整锁临界区(lock与Unlock之间的代码区域)的代码。


参考资料:

参考实验文档golang pprof 实战

深入golang -- GODEBUG与性能分析

课程地址性能调优实战案例