这是我参与「第六届青训营 -后端场」笔记创作活动的的第3篇笔记。
性能优化的作用
经过我这些天的学习,我先来说说我了解到的性能优化的好处。
- 提高响应速度:性能优化可以减少代码的执行时间,从而提高系统的响应速度。通过使用一些方法,可以减少不必要的计算和等待时间,更快地结束任务。
- 降低资源消耗:性能优化可以减少代码对系统资源的需求,如CPU、内存和磁盘空间等。
- 优化算法和数据结构:选择合适的算法和数据结构是性能优化这很关键。比如说,选择具有较低时间复杂度的算法可以显著提高代码的执行效率。
- 减少网络延迟:对于涉及网络通信的代码项目,性能优化可以减少网络延迟,提高数据传输的速度和效率。
- 内存管理和垃圾回收:性能优化至有助于进行合理的内存管理和垃圾回收。
图片优化
- 选择合适的图片格式:根据不同的需求和场景,选择合适的图片格式,如JPEG、PNG、WebP等。JPEG适用于照片等真彩色图片,而PNG适用于有透明度要求的图片,WebP是一种新兴的图片格式,在保持图片质量的同时能大幅减小文件大小。
- 压缩图片大小:使用专业的图片压缩工具,如ImageOptim、TinyPNG等,压缩图片的文件大小,以减少加载时间。同时,可以设置适当的图片尺寸以及裁剪不必要的部分,进一步减小图片的大小。
- 使用图片懒加载:对于页面上的大量图片,可以采用图片懒加载技术,即在用户滚动到可见区域时再加载图片,以提高初始加载速度。
前端资源优化
- 合并和压缩CSS和JavaScript文件:将多个CSS和JavaScript文件合并为一个文件,并使用压缩工具对文件进行压缩,以减少HTTP请求和文件大小。
- 使用浏览器缓存:通过设置适当的缓存策略,让浏览器缓存静态资源文件,并在后续访问中直接从缓存中加载文件,减少网络请求。
- 异步加载脚本:将一些不影响页面渲染的脚本标记为异步加载,以避免阻塞页面加载。
数据请求优化
- 减少HTTP请求次数:合并多个请求,减少不必要的网络请求次数。例如,可以将多个API请求合并为一个请求,或者使用数据缓存技术,避免重复请求相同的数据。
- 使用CDN加速:将静态资源部署到CDN(内容分发网络),使用户能够从离自己最近的节点获取资源,减少网络延迟和提高加载速度。
- 数据压缩:对于传输的大量数据,可以使用数据压缩算法,如Gzip,减小数据大小,提高传输效率。
性能优化建议
slice预分配内存
尽可能在使用make()初始化切片时提供容量信息,能够避免底层的内存分配次数
指定slice的时候没有指定大小
在初始化的时候指定了大小
如果我们提前分配的话,它的执行时间大概是原来的三分之之一左右(缩短时间)
如果容量足够,新增元素直接放进数组里。
但若容量不够,底层数组先有扩容的操作,然后再把实际内容添加到数组中。
由于操作耗时,容易造成性能差,为了避免内存发生copy的过程,
所以先在初始化时设置合适的值,避免额外的分配,从而获得更好的性能。
在已有切片基础上创建切片,不会创建底层数组,这会导致
- 原切片较大,代码在原切片基础上新建小切片
- 原底层数组在内存中有引用,得不到释放
所以我们可以使用copy代替re-slice
map预分配内存
不断向map中添加元素的操作会触发 map 的扩容 根据实际需求提前预估好需要的空间 提前分配好空间可以减少内存copy和 Rehash 的消耗
字符串处理
字符串在 Go 语言中是不可变类型,占用内存大小是固定的
使用 + 拼接 字符串每次都会重新开辟新空间
+ : 最慢
strings.Builder:最快,内存以倍数申请,底层是[]byte数组,直接将底层的 []byte 转换成了字符串类型返回。
bytes.Buffer :较快,内存以倍数申请,底层是 []byte 数组,转化为字符串重新申请空间存放生成的字符串变量。
使用空结构体节省内存
空结构体不占据内存空间,可作为占位符使用。
例如Set,Go 语言标准库没有提供 Set 的实现,通常使用 map 来代替。
对于集合场景,只需要用到 map 的键而不需要值。
实战案例
下载测试代码
在终端里运行go get 中可以获取测试程序, 注意加上 -d 参数,避免下载后自动安装:
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
运行后进行测试
运行后会不断打印日志,以下是内容(意义不大,不必细看):
这一步用来测试资源吃否吃紧,能否扛住一分钟,如果可以,便可以进行下一步了。
使用pproof
保持程序运行,打开浏览器访问 http://localhost:6060/debug/pprof/
可以看到以下内容:
排场
go tool pprof命令不但可以使用交互式终端,也可以使用web进行可视化分析,此外可以直接将数据生成svg图片,进行静态的分析。
go tool pprof http://localhost:6060/debug/pprof/profile
根据上图左边的数量可以看出需要排场的方面。
后续例如,排场内存占比时,此时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命令,我们可以查看代码具体的位置。
定位到24行,可见有个一百亿次空循环占用了大量 CPU 时间。
图形化显示调用栈信息
访问 graphviz 官网寻找适配自己操作系统的安装包,或者输入命令下载。
(这里我的电脑型号用不了这个命令,也可能有其他操作,但是我没试成功,我选择了直接在官网下载)
web命令
输入命令后会弹出一个.svg文件的弹框,选择用浏览器打开。
会看到类似这样的框图
//我刚开始准备做这个实验的时候,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
截取了一部分示例
提一下实验里排查到dog.run 这一块
存在无意义的内存申请,而这个函数又会被频繁调用,就导致了程序不停地进行 GC
当我们把问题代码注释掉
func (d *Dog) Run() {
log.Println(d.Name(), "run")
//_ = make([]byte, 16 * constant.Mi)
}
就会发现短时间内未打印 GC 日志了。
排查分析
框图大也并不代表原因直接出在里边,例如:
我们可以通过追根溯源,发现
github.com/wolfogre/go-pprof-practice/animal/canidae/wolf.(*Wolf).Drink
是这一句不断地在创建没有必要的协程
协程泄露
我们发现大量的协程调用栈最终都会指向 runtime.gopark 函数。
这个函数的作用就是将协程变成等待状态 _Gwaiting,随着 goroutine 越来越多最终耗尽内存,内存溢出。
mutex阻塞分析
在分析报告中我们发现互斥锁带来的协程休眠时间是30s 左右
cpu利用率不高,浪费了大量的时间
所以需要调整锁临界区(lock与Unlock之间的代码区域)的代码。
参考资料:
参考实验文档golang pprof 实战
课程地址性能调优实战案例