CSDIY:这是一个非科班学生的努力之路,从今天开始这个系列会长期更新,(最好做到日更),我会慢慢把自己目前对CS的努力逐一上传,帮助那些和我一样有着梦想的玩家取得胜利!!!
第一弹:Cpp零基础学习【30 DAYS 从0到1】
第二弹:Cpp刷题文档【LeetCode】
第三弹:Go开发入门【字节后端青训营】
第四弹:Cpp简单项目开发【黑马Rookie】
第五弹:数据结构绪论【数据结构与算法】
第六弹:Go工程实践【字节后端青训营】
第七弹:高质量编程和性能调优【字节后端青训营】
241118——[ByteDance] [02] 高质量编程与性能调优实践
实际开发中,不仅仅需要实现功能,也要让团队其他人看懂。
- 优秀的功能和效率
- 清晰的算法思路和编程习惯
1. 高质量编程
正确可靠,清晰简介的代码是受欢迎的
1.1 高质量编程简介
- 各种边界条件是否考虑完备
- 异常情况处理,稳定性保证
- 易读易维护
Go:编程原则 - Go 语言开发者 Dave Cheney
- 简单性:消除多余的“复杂性”,人无法理解的代码无法修复改进
- 可读性:代码是写给人看的
- 生产力:团队整体工作效率很重要
1.2 编码规范
如何编写高质量的 Go 代码
- 代码格式
- 注释
- 命名规范
- 控制流程
- 错误和异常处理
1.2.1 代码格式
推荐使用 gofmt 自动格式化代码
gofmt / goimports (对依赖包进行增删排序)
某些 IDE 自带格式化功能
1.2.2 注释
Good code has lots of comments, bad code requires lots of comments.
——Dave Thomas and Andrew Hunt
公共符号:
一些常量,公开变量
公共符号始终要注释
但有一个例外:不需要注释实现接口的方法。具体不要让开发者去溯源?
实现过程:
代码是怎么实现的,在维护代码时也要维护注释
实现原因:
提供适合的上下文
限制条件:
解释代码在什么情况下会出错
小结:
- 代码是最好的注释
- 注释应该提供代码未表达出的上下文信息 而不是逐句逐语法解释
1.2.3 命名规范
variab:
- 简介胜于冗长
- 缩略词全大写
- 变量距离被使用的地方越远,越需要携带更多的上下文信息
function:
- 函数名不携带包名的上下文信息
- 函数名尽量简短
- 函数名可以在不影响歧义的情况下,可以在函数名加入类型信息
package:
- 只由小写字母组成
- 简短且携带一定信息
- 不要与标准库同名
尽量满足
- 不使用常用变量名作为包名
- 使用单数而不是复数
- 谨慎使用缩写( format > fmt )
小结:
- 核心目标是降低阅读理解代码的成本
- 重点考虑上下文信息,设计简介清晰的名称
1.2.4 控制流程
-
避免 if - else 分支嵌套
-
尽量保持正常代码路径为最小缩进
小结:
- 线性原理
- 故障问题大多都出现在复杂的条件语句中
1.2.5 错误和异常处理
简单错误:仅出现一次的错误
复杂错误:
错误的 Wrap 和 Unwrap
错误判定:
判断一个错误是否为特定错误
在错误链上获取特定种类的错误
panic:
- 不建议在业务代码中使用 panic
- 若问题可以被屏蔽或解决,建议使用 error 代替 panic
- 当程序启动阶段发生不可逆转的错误时,可以在 main 函数中使用 panic
recover:
-
recover 可以用来解决 panic 引发的进一步错误
-
recover 只能被 defer 函数中使用
-
在 recover 后在 log 中记录当前调用栈
小结:
- error 尽可能提供简明的上下文信息连,方便定位问题
- panic 用于真正异常的情况
- recover 生效范围:在当前 goroutine 的被 defer 的函数中生效
1.3 性能优化建议
性能优化的前提是满足正确可靠、简介清晰
1.3.1 Benchmark
Go 自带的工具
go test -bench=. -benchmen
1.3.2 Slice
预分配内存(避免频繁扩容)
1.3.3 Map
预分配内存
提前分配好内存可以减少内存和 rehash 的消耗
1.3.4 字符串处理
string.Builder : 转化为字符串时 重新申请了一块空间
实际上也是预分配的逻辑
1.3.5 空结构体
空结构体节省内存
- 节省资源
- 不需要任何值,仅仅做占位符
可以用来实现 Set,此时只需要键,而不需要值
1.3.6 atomic 包
- 锁的实现通过操作系统实现,属于系统调用
- 通过硬件实现
小结:
- 避免常见性能陷阱
- 普通应用代码,不要一味追求i性能
- 越高级的性能手段越容易出问题
2. 性能调优实战
学校里学不到的!
2.1 性能调优简介
- 要依靠数据而不是猜测
- 要定位最大瓶颈而不是细枝末节
- 不要过早优化
- 不要过度优化
2.2 性能分析工具 pprof 实战
说明
- 希望知道在什么地方耗费了多少CPU、Memory
- 可视化分析
开始之前:请查看完整教程:
https://blog.wolfogre.com/posts/go-ppof-practice/
如果要说在 golang 开发过程进行性能调优,pprof 一定是一个大杀器般的工具。但在网上找到的教程都偏向简略,难寻真的能应用于实战的教程。这也无可厚非,毕竟 pprof 是当程序占用资源异常时才需要启用的工具,而我相信大家的编码水平和排场问题的能力是足够高的,一般不会写出性能极度堪忧的程序,且即使发现有一些资源异常占用,也会通过排查代码快速定位,这也导致 pprof 需要上战场的机会少之又少。即使大家有心想学习使用 pprof,却也常常相忘于江湖。
既然如此,那我就送大家一个性能极度堪忧的“炸弹”程序吧!
这程序没啥正经用途缺极度占用资源,基本覆盖了常见的性能问题。本文就是希望读者能一步一步按照提示,使用 pprof 定位这个程序的的性能瓶颈所在,借此学习 pprof 工具的使用方法。
因此,本文是一场“实验课”而非“理论课”,请读者腾出时间,脚踏实地,一步一步随实验步骤进行操作,这会是一个很有趣的冒险,不会很无聊,希望你能喜欢。
2.2.1 功能简介
- Tool工具
- Sample采样:CPU、Heap、Goroutine
- Profile分析:网页
- View展示:Top、Graph、FlameGraph、Source
搭建 pprof 项目
2.2.2 排查实战
保持程序运行,打开浏览器访问 http://localhost:6060/debug/pprof/
可以查看指标:数据平铺
查看 CPU 情况:
查看占用资源最多的函数
命令:top
flat | flat% | sum% | cum | cum% |
---|---|---|---|---|
当前函数本身执行耗时 | flat占 CPU 总时间的比例 | 上面每一行的 flat% 总和 | 当前函数本身加上其调用函数的总耗时 | cum 占CPU 总时间的比例 |
Flat == Cum,函数中没有调用其他函数
Flat == 0,函数中只有其他函数的调用
命令:list
根据指定正则表达式查找代码行
命令:web
生成一个调用关系图,使得调用关系可视化
heap-堆内存
goroutine-协程
- goroutine 泄露会导致内存泄漏
mutex-锁
block-阻塞
2.2.3 采样过程和原理
CPU
- 采样对象:函数调用和它们占用的时间
- 采样率:100次/秒,固定值
- 采样时间:从手动启动到手动结束
- 操作系统
- 进程
- 写缓冲
Heap - 堆内存
- 采样率:每分配512KB记录一次
- 采样时间:从程序运行开始到采样时
Goroutine - 协程 & ThreadCreate - 线程创建
- Goroutine - 协程:记录所有用户发起且在运行中的 Goroutine
- ThreadCreate - 线程创建:记录程序创建所有系统线程的信息
Bolck - 阻塞 & Mutex - 锁
- 阻塞操作:采样阻塞操作的次数和耗时
- 锁竞争:采样争抢锁的次数和耗时
2.3 性能调优案例
-
实际业务服务性能调优案例
-
对逻辑相对复杂的程序如何进行性能调优
2.3.1 业务服务优化
基本概念
- 服务:能单独部署,承载一定功能的程序
- 依赖:
- 调用链路:能支持一个接口请求的相关服务集合及其相互之间的依赖关系
- 基础库:公共的工具包
流程
- 建立服务性能评估手段
- 分析性能数据,定位性能瓶颈
- 重点优化项改造
- 优化效果验证
建立服务性能评估手段
- 服务性能评估方式
- 请求流量构造
- 压测范围
- 性能数据采集
分析性能数据,定位性能瓶颈
- 使用库不规范
- 高并发场景优化不足
重点优化项改造
- 正确性
- 响应数据
优化效果验证
- 重复压测验证
- 上限评估优化结果
进一步优化,服务整体链路分析
- 规范上游服务调用接口
- 分析链路
2.3.2 基础库优化
AB 实验 SDK 优化
- 分析基础库核心逻辑
- 内部压测验证
- 推广业务服务落地验证
2.3.3 Go 语言优化
编译器 & 运行时优化
- 优化内存分配策略
- 优化代码编译流程
- 内部压测验证
- 推广业务服务落地验证
2.4 总结
- 性能调优原则
- 要依靠数据而不是猜测
- 性能调优工具 pprof
- 熟练使用 pprof 工具排查性能问题并了解其基本原理
- 性能调优
- 保证正确性
- 定位主要瓶颈
碎碎念:每周一可以说是最怕的日子,有很多DDL,但还是挺过来了。跟上字节的课可以说有点困难。。。很多时候都想放弃,但是后端入门篇就这样被我水过去了…谁知道呢,后面的课程会更加虐,但我一想到CSDIY里面的话,我就心有余甘…很多时也想过放弃和偷懒,也想过自己是不是在假努力,但谁说学了一定要会呢?我就是喜欢做笔记的感觉,我就是喜欢敲键盘(打字)怎么了???所以呢,还是不想给自己借口。
与君共勉。